Consentimiento PDF completo

main
Juan David Lopez Regalado 1 month ago
parent 6e217d812a
commit a2c0a2fe95

@ -388,3 +388,183 @@ Al finalizar, se puede correr la aplicación y observar las diferentes secciones
Debido a que los desarroladores de Apple, utilizan el idioma para hacer sus aplicaciones y el soporte, algunas de las secciones en la aplicación, podrían estar en idioma inglés. Debido a que los desarroladores de Apple, utilizan el idioma para hacer sus aplicaciones y el soporte, algunas de las secciones en la aplicación, podrían estar en idioma inglés.
Al tener terminado el consentimiento, se tendrá que enviar a una nueva pantalla donde se encuentran los formularios, tareas y demás actividades que ResearchKit permite hacer, ya que al momento de terminar solo se regresa a la pantalla principal.
Para hacer este cambio de cuando el consentimiento este completo, se crea una condicion *if* en el **IntroViewController** que indique que si se completó el consentimiento, proceda a la pantalla de **TasksViewController**, o e su defecto que haga efecto el segue, toTasks.
```
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if ORKPasscodeViewController.isPasscodeStoredInKeychain(){
toTasks()
}else{
toConsent()
}
}
```
Sin olvidarse de importar ResearchKitUI
Además, se tiene que llevar al usuario al controlador de vista(TasksViewController) de tareas cuando se completen las tareas del consentimiento. Por ello, se agrega el sigueinte código al **IntroViewController**:
```
@IBAction func unwindToTasks(_ segue: UIStoryboardSegue){
toTasks()
}
```
y desde el main.storyboard se debe arrastrar con click derecho desde la pantalla de ConsentViewController del boton principal, (ConsentViewController) hasta el de exit y seleccionar la unica opcion que hay que es unwindTotask.
![](imagenes/unwind1.png)
![](imagenes/unwind2.png)
y agregaremos un identificador al segue con el nombre unwindToTask.
![](imagenes/unwind3.png)
Se dirigirá al ConsentViewController en la funcion taskViewController que se encuentra en la parte inferior y la modificará para que haga la correcta transición del nuevo segue:
```
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) {
switch reason {
case .completed:
performSegue(withIdentifier: "unwindToTasks", sender: nil)
case .discarded:
dismiss(animated: true, completion: nil)
case .failed:
dismiss(animated: true, completion: nil)
case .saved:
dismiss(animated: true, completion: nil)
case .earlyTermination:
dismiss(animated: true, completion: nil)
@unknown default:
dismiss(animated: true, completion: nil)
}
}
```
En el TasksViewController del Main.storyboard se requiere crear un nuevo botón para el abandono del estudio y así poder regresar a la pantalla de inicio.
![](imagenes/bAbandonar.png)
y para hacer el regreso a la pantalla de Consent al presionar el botón de abandonar, se crea, un segue de tipo show hasta el ConsentViewController. Y a este segue, se le pondrá un identificador que lleve por nombre returnToConsent:
![](imagenes/returnC.png)
![](imagenes/returnC1.png)
![](imagenes/returnC2.png)
Para hacer que funcione este segue al presionar el botón de abandonar el estudio, en el TasksViewController, es necesario crear un ButtonAction de tipo UIButton
con el nombre leaveButtonTapped.
![](imagenes/Bleave.png)
en el código generado se pondra el siguiente código, el cual elimina el almacén de contraseñas y presenta el ConsentViewController:
```
ORKPasscodeViewController.removePasscodeFromKeychain()
performSegue(withIdentifier: "returnToConsent", sender: nil)
```
De esta manera, el usuario abandonará el estudio, y si desea unirse de nuevo, se le presentará el documento de consentimiento y se le pedirá que cree un código de acceso.
Afortunadamente, ResearchKit no solo tiene la herramienta para visualizar el proceso de consentimiento, sino también la herramienta para guardar el documento de consentimiento firmado como PDF.
Para hacer la visualización del consentimiento se tiene que modificar desde el **ConsentViewController** la función *func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) {*
con el sigueinte código:
```
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) {
switch reason {
case .completed:
// Obtener el resultado de la firma
guard let signatureResult = taskViewController.result.stepResult(forStepIdentifier: "ConsentReviewStep")?.firstResult as? ORKConsentSignatureResult else {
print("No se pudo obtener el resultado de la firma.")
return
}
// Crear una copia del documento de consentimiento
let consentDocument = ConsentDocument.copy() as! ORKConsentDocument
signatureResult.apply(to: consentDocument)
// Generar el PDF
consentDocument.makePDF { (data, error) in
if let error = error {
print("Error al crear PDF: \(error.localizedDescription)")
return
}
guard let data = data else {
print("No se generó ningún dato para el PDF.")
return
}
// Guardar el PDF en el directorio de documentos
do {
var docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last
docURL = docURL?.appendingPathComponent("consent.pdf")
try data.write(to: docURL!, options: .atomicWrite)
print("PDF guardado en: \(docURL!.path)")
} catch {
print("Error al guardar el PDF: \(error.localizedDescription)")
}
}
// Realizar la transición al siguiente controlador
performSegue(withIdentifier: "unwindToTasks", sender: nil)
case .discarded, .failed, .saved:
dismiss(animated: true, completion: nil)
case .earlyTermination:
dismiss(animated: true, completion: nil)
@unknown default:
// Manejar casos no contemplados
dismiss(animated: true, completion: nil)
}
}
```
y se creará un nuevo botón en la pantalla del **TasksViewController** donde al presionar se pueda visualizar el consentimiento que se firmó.
Al botón generado se le generará un Button Action con el Nombre consentButtonTapped de tipo UIButton, directamente ne **TasksViewController**.
![](imagenes/consentPDF.png)
y se le agrega el siguiente código para generar el PDF.
```
@IBAction func consentButtonTapped(_ sender: UIButton) {
let taskViewController = ORKTaskViewController(task: consentPDFViewerTask(), taskRun: nil)
taskViewController.delegate = self
present(taskViewController, animated: true, completion: nil)
}
func consentPDFViewerTask() -> ORKOrderedTask{
var docURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last
docURL = docURL?.appendingPathComponent("consent.pdf")
let PDFViewerStep = ORKPDFViewerStep.init(identifier: "ConsentPDFViewer", pdfURL: docURL)
PDFViewerStep.title = "Consent"
return ORKOrderedTask(identifier: String("ConsentPDF"), steps: [PDFViewerStep])
}
@objc(taskViewController:didFinishWithReason:error:) func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) {
taskViewController.dismiss(animated: true, completion: nil)
}
```
sin olvidar agregar *ORKTaskViewControllerDelegate* en la clase principal
Para comprobar el funcionameinto, se procede a realizar el consentimiento y al teminar, se da click en el Consentimiento PDF y generará algo como lo siguiente:
![](imagenes/pdfC.png)

@ -27,31 +27,65 @@
</objects> </objects>
<point key="canvasLocation" x="-1260" y="56"/> <point key="canvasLocation" x="-1260" y="56"/>
</scene> </scene>
<!--Task View Controller--> <!--Tasks View Controller-->
<scene sceneID="cjp-pL-hfK"> <scene sceneID="cjp-pL-hfK">
<objects> <objects>
<viewController id="JfP-LC-owM" customClass="TaskViewController" sceneMemberID="viewController"> <viewController id="JfP-LC-owM" customClass="TasksViewController" customModule="RK_Journals" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="ICa-LQ-fV8"> <view key="view" contentMode="scaleToFill" id="ICa-LQ-fV8">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i24-wX-q8O">
<rect key="frame" x="74" y="702" width="244" height="42"/>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" title="Abandonar estudio">
<fontDescription key="titleFontDescription" type="system" pointSize="23"/>
<color key="baseForegroundColor" systemColor="systemPinkColor"/>
</buttonConfiguration>
<connections>
<action selector="leaveButtonTapped:" destination="JfP-LC-owM" eventType="touchUpInside" id="maF-HC-hAn"/>
<segue destination="B7d-i6-kuM" kind="show" identifier="returnToConsent" id="JAR-sR-Aer"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="T7B-Od-def">
<rect key="frame" x="98" y="638" width="197" height="37"/>
<state key="normal" title="Button"/>
<buttonConfiguration key="configuration" style="plain" title="Consentimiento PDF">
<fontDescription key="titleFontDescription" type="system" pointSize="19"/>
</buttonConfiguration>
<connections>
<action selector="consentButtonTapped:" destination="JfP-LC-owM" eventType="touchUpInside" id="MFB-Vp-wFc"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="juQ-Ke-iUj"/> <viewLayoutGuide key="safeArea" id="juQ-Ke-iUj"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="juQ-Ke-iUj" firstAttribute="trailing" secondItem="T7B-Od-def" secondAttribute="trailing" constant="98" id="3iY-qu-0Yn"/>
<constraint firstItem="i24-wX-q8O" firstAttribute="top" secondItem="juQ-Ke-iUj" secondAttribute="top" constant="643" id="LlO-qa-dTb"/>
<constraint firstItem="T7B-Od-def" firstAttribute="top" secondItem="juQ-Ke-iUj" secondAttribute="top" constant="579" id="PKj-be-ciE"/>
<constraint firstItem="juQ-Ke-iUj" firstAttribute="bottom" secondItem="i24-wX-q8O" secondAttribute="bottom" constant="74" id="Xs7-X3-MJu"/>
<constraint firstItem="T7B-Od-def" firstAttribute="leading" secondItem="juQ-Ke-iUj" secondAttribute="leading" constant="98" id="bdF-cj-wl8"/>
<constraint firstItem="juQ-Ke-iUj" firstAttribute="bottom" secondItem="T7B-Od-def" secondAttribute="bottom" constant="143" id="frs-Wr-4Bn"/>
<constraint firstItem="juQ-Ke-iUj" firstAttribute="trailing" secondItem="i24-wX-q8O" secondAttribute="trailing" constant="75" id="jlf-Py-DdG"/>
<constraint firstItem="i24-wX-q8O" firstAttribute="leading" secondItem="juQ-Ke-iUj" secondAttribute="leading" constant="74" id="r3t-fF-Avc"/>
</constraints>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="5pU-sM-lm9" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="5pU-sM-lm9" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="-211" y="456"/> <point key="canvasLocation" x="-211.4503816793893" y="455.63380281690144"/>
</scene> </scene>
<!--Consent View Controller--> <!--Consent View Controller-->
<scene sceneID="acU-c7-nQQ"> <scene sceneID="acU-c7-nQQ">
<objects> <objects>
<viewController id="B7d-i6-kuM" customClass="ConsentViewController" customModule="RK_Journals" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="B7d-i6-kuM" customClass="ConsentViewController" customModule="RK_Journals" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="qDt-sx-u4m"> <view key="view" contentMode="scaleToFill" id="qDt-sx-u4m">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/> <rect key="frame" x="0.0" y="0.0" width="393" height="842"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pj0-Qw-cYF"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pj0-Qw-cYF">
<rect key="frame" x="60" y="191" width="273" height="35"/> <rect key="frame" x="60" y="132" width="273" height="118"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/> <fontDescription key="fontDescription" type="system" pointSize="24"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/> <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Unirse al estudio"/> <state key="normal" title="Unirse al estudio"/>
@ -69,15 +103,26 @@
<constraint firstItem="Sil-rc-UTE" firstAttribute="bottom" secondItem="pj0-Qw-cYF" secondAttribute="bottom" constant="592" id="Xi6-DA-aF0"/> <constraint firstItem="Sil-rc-UTE" firstAttribute="bottom" secondItem="pj0-Qw-cYF" secondAttribute="bottom" constant="592" id="Xi6-DA-aF0"/>
</constraints> </constraints>
</view> </view>
<navigationItem key="navigationItem" id="5yH-CR-B3d"/>
<connections>
<segue destination="U0R-T7-m5E" kind="unwind" identifier="unwindToTasks" unwindAction="unwindToTasks:" id="QFc-gc-jVy"/>
</connections>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Lae-n8-OZT" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="Lae-n8-OZT" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
<exit id="U0R-T7-m5E" userLabel="Exit" sceneMemberID="exit"/>
</objects> </objects>
<point key="canvasLocation" x="-211.4503816793893" y="-184.50704225352115"/> <point key="canvasLocation" x="-211.4503816793893" y="-184.50704225352115"/>
</scene> </scene>
</scenes> </scenes>
<inferredMetricsTieBreakers>
<segue reference="JAR-sR-Aer"/>
</inferredMetricsTieBreakers>
<resources> <resources>
<systemColor name="systemBackgroundColor"> <systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor> </systemColor>
<systemColor name="systemPinkColor">
<color red="1" green="0.1764705882" blue="0.33333333329999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources> </resources>
</document> </document>

@ -24,10 +24,59 @@ class ConsentViewController: UIViewController, ORKTaskViewControllerDelegate {
} }
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) { func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) {
dismiss(animated: true, completion: nil) //Dismisses the view controller when we finish our consent task
switch reason {
case .completed:
// Obtener el resultado de la firma
guard let signatureResult = taskViewController.result.stepResult(forStepIdentifier: "ConsentReviewStep")?.firstResult as? ORKConsentSignatureResult else {
print("No se pudo obtener el resultado de la firma.")
return
} }
// Crear una copia del documento de consentimiento
let consentDocument = ConsentDocument.copy() as! ORKConsentDocument
signatureResult.apply(to: consentDocument)
// Generar el PDF
consentDocument.makePDF { (data, error) in
if let error = error {
print("Error al crear PDF: \(error.localizedDescription)")
return
}
guard let data = data else {
print("No se generó ningún dato para el PDF.")
return
}
// Guardar el PDF en el directorio de documentos
do {
var docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last
docURL = docURL?.appendingPathComponent("consent.pdf")
try data.write(to: docURL!, options: .atomicWrite)
print("PDF guardado en: \(docURL!.path)")
} catch {
print("Error al guardar el PDF: \(error.localizedDescription)")
}
}
// Realizar la transición al siguiente controlador
performSegue(withIdentifier: "unwindToTasks", sender: nil)
case .discarded, .failed, .saved:
dismiss(animated: true, completion: nil)
case .earlyTermination:
dismiss(animated: true, completion: nil)
@unknown default:
// Manejar casos no contemplados
dismiss(animated: true, completion: nil)
}
}
} }

@ -6,14 +6,19 @@
// //
import UIKit import UIKit
import ResearchKitUI
class IntroViewController: UIViewController { class IntroViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib. // Do any additional setup after loading the view, typically from a nib.
if ORKPasscodeViewController.isPasscodeStoredInKeychain(){
toTasks()
}else{
toConsent() toConsent()
} }
}
override func didReceiveMemoryWarning() { override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning() super.didReceiveMemoryWarning()
@ -27,4 +32,8 @@ class IntroViewController: UIViewController {
func toTasks(){ func toTasks(){
performSegue(withIdentifier: "toTasks", sender: self) performSegue(withIdentifier: "toTasks", sender: self)
} }
@IBAction func unwindToTasks(_ segue: UIStoryboardSegue){
toTasks()
}
} }

@ -6,8 +6,9 @@
// //
import UIKit import UIKit
import ResearchKitUI
class TasksViewController: UIViewController { class TasksViewController: UIViewController, ORKTaskViewControllerDelegate {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -26,4 +27,27 @@ class TasksViewController: UIViewController {
} }
*/ */
@IBAction func leaveButtonTapped(_ sender: UIButton) {
ORKPasscodeViewController.removePasscodeFromKeychain()
performSegue(withIdentifier: "returnToConsent", sender: nil)
}
@IBAction func consentButtonTapped(_ sender: UIButton) {
let taskViewController = ORKTaskViewController(task: consentPDFViewerTask(), taskRun: nil)
taskViewController.delegate = self
present(taskViewController, animated: true, completion: nil)
}
func consentPDFViewerTask() -> ORKOrderedTask{
var docURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last
docURL = docURL?.appendingPathComponent("consent.pdf")
let PDFViewerStep = ORKPDFViewerStep.init(identifier: "ConsentPDFViewer", pdfURL: docURL)
PDFViewerStep.title = "Consent"
return ORKOrderedTask(identifier: String("ConsentPDF"), steps: [PDFViewerStep])
}
@objc(taskViewController:didFinishWithReason:error:) func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) {
taskViewController.dismiss(animated: true, completion: nil)
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Loading…
Cancel
Save