Clonación de la aplicación, detalles a corregir
parent
66c18e83e3
commit
a2b7c60346
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.healthkit</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.healthkit.access</key>
|
||||
<array>
|
||||
<string>health-records</string>
|
||||
</array>
|
||||
<key>com.apple.developer.healthkit.background-delivery</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Geometric Draw.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Pencil.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Reloj antiguo.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 2.9 MiB |
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icons8-google(1).svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,75.686276%,2.745098%);fill-opacity:1;" d="M 21.804688 10.042969 L 21 10.042969 L 21 10 L 12 10 L 12 14 L 17.652344 14 C 16.828125 16.328125 14.613281 18 12 18 C 8.6875 18 6 15.3125 6 12 C 6 8.6875 8.6875 6 12 6 C 13.53125 6 14.921875 6.578125 15.980469 7.519531 L 18.808594 4.691406 C 17.023438 3.027344 14.632812 2 12 2 C 6.476562 2 2 6.476562 2 12 C 2 17.523438 6.476562 22 12 22 C 17.523438 22 22 17.523438 22 12 C 22 11.328125 21.929688 10.675781 21.804688 10.042969 Z M 21.804688 10.042969 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,23.921569%,0%);fill-opacity:1;" d="M 3.152344 7.34375 L 6.4375 9.753906 C 7.328125 7.554688 9.480469 6 12 6 C 13.53125 6 14.921875 6.578125 15.980469 7.519531 L 18.808594 4.691406 C 17.023438 3.027344 14.632812 2 12 2 C 8.160156 2 4.828125 4.167969 3.152344 7.34375 Z M 3.152344 7.34375 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(29.803923%,68.627453%,31.37255%);fill-opacity:1;" d="M 12 22 C 14.582031 22 16.929688 21.011719 18.703125 19.402344 L 15.609375 16.785156 C 14.605469 17.546875 13.359375 18 12 18 C 9.398438 18 7.191406 16.339844 6.359375 14.027344 L 3.097656 16.539062 C 4.753906 19.777344 8.113281 22 12 22 Z M 12 22 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(9.803922%,46.27451%,82.352942%);fill-opacity:1;" d="M 21.804688 10.042969 L 21 10.042969 L 21 10 L 12 10 L 12 14 L 17.652344 14 C 17.253906 15.117188 16.535156 16.082031 15.609375 16.785156 L 18.703125 19.402344 C 18.484375 19.601562 22 17 22 12 C 22 11.328125 21.929688 10.675781 21.804688 10.042969 Z M 21.804688 10.042969 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
@ -0,0 +1,85 @@
|
||||
//
|
||||
// ActivitiesViewController.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 14/10/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ActivitiesViewController: UIViewController, BluetoothManagerDelegate {
|
||||
|
||||
@IBOutlet weak var getDataButton: UIButton!
|
||||
@IBOutlet weak var stateLabel: UILabel!
|
||||
@IBOutlet weak var dataLabel: UILabel!
|
||||
@IBOutlet weak var connectButton: UIButton!
|
||||
@IBOutlet weak var endActivityButton: UIButton!
|
||||
@IBOutlet weak var startActivityButton: UIButton!
|
||||
@IBOutlet weak var disconnectButton: UIButton!
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
title = "Actividades"
|
||||
|
||||
BluetoothManager.shared.delegate = self
|
||||
}
|
||||
|
||||
@IBAction func connectButtonTapped(_ sender: UIButton) {
|
||||
BluetoothManager.shared.startScanning()
|
||||
}
|
||||
|
||||
@IBAction func disconnectButtonTapped(_ sender: UIButton) {
|
||||
BluetoothManager.shared.disconnect()
|
||||
}
|
||||
|
||||
@IBAction func startActivityButtonTapped(_ sender: UIButton) {
|
||||
BluetoothManager.shared.sendCommand("MEASURE")
|
||||
}
|
||||
@IBAction func endActivityButtonTapped(_ sender: UIButton) {
|
||||
BluetoothManager.shared.sendCommand("IDLE")
|
||||
}
|
||||
|
||||
@IBAction func cancelActivityButtonTapped(_ sender: UIButton) {
|
||||
BluetoothManager.shared.sendCommand("CANCEL")
|
||||
}
|
||||
@IBAction func getDataButtonTapped(_ sender: UIButton) {
|
||||
BluetoothManager.shared.sendCommand("SEND_DATA")
|
||||
}
|
||||
|
||||
// BluetoothManagerDelegate - Actualizar estado de conexión
|
||||
func didUpdateConnectionStatus(isConnected: Bool) {
|
||||
let status = isConnected ? "Conectado": "Desconectado"
|
||||
print("Estado de conexión: \(status)")
|
||||
print("En activities view controller")
|
||||
DispatchQueue.main.async {
|
||||
self.stateLabel.text = "Estado: " + status
|
||||
}
|
||||
}
|
||||
|
||||
// BluetoothManagerDelegate - Datos recibidos
|
||||
func didReceiveData(_ data: String) {
|
||||
print("Datos recibidos: \(data)")
|
||||
print("En activities view controller")
|
||||
if self.isViewLoaded && self.view.window != nil {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.showAlert(title: "Datos del ESP32", mmessage: "Datos recibidos: \(data)")
|
||||
}
|
||||
} else {
|
||||
print("La vista NO está en la jerarquía de vistas")
|
||||
}
|
||||
}
|
||||
|
||||
// BluetoothManagerDelegate - Datos recibidos
|
||||
func didReceiveData(fromCharacteristic uuid: String, data: String) {
|
||||
if uuid == "beb5483e-36e1-4688-b7f5-ea07361b26a8" {
|
||||
print("Comando recibido: \(data)")
|
||||
} else if uuid == "beb5483e-36e1-4688-b7f5-ea07361b26b1" {
|
||||
print("Datos del MAX3010: \(data)")
|
||||
} else if uuid == "beb5483e-36e1-4688-b7f5-ea07361b26b2" {
|
||||
print("Datos del MPU6050: \(data)")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
//
|
||||
// Alerts.swift
|
||||
// App-RK-BM
|
||||
//
|
||||
// Created by Luis Mora on 11/11/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
func showAlert(title: String, mmessage: String, actionTitle: String = "OK", accionHandler: ((UIAlertAction) -> Void)? = nil) {
|
||||
let alerta = UIAlertController(title: title, message: mmessage, preferredStyle: .alert)
|
||||
let accion = UIAlertAction(title: actionTitle, style: .default, handler: accionHandler)
|
||||
alerta.addAction(accion)
|
||||
self.present(alerta, animated: true, completion: nil)
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
//
|
||||
// ConsentTask.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 11/10/24.
|
||||
//
|
||||
|
||||
import ResearchKit
|
||||
import ResearchKitUI
|
||||
import ResearchKitActiveTask
|
||||
|
||||
public var ConsentTask: ORKOrderedTask{
|
||||
|
||||
var steps = [ORKStep]()
|
||||
|
||||
//Visualization
|
||||
//This step is where Apple will help us present the consent document with animations
|
||||
let consentDocument = ConsentDocument
|
||||
|
||||
//Request Health Data
|
||||
//We will need the user to grant us access to health data for collection
|
||||
let healthDataStep = HealthDataAuthStep(identifier: "Health")
|
||||
steps += [healthDataStep]
|
||||
|
||||
//Review & Sign
|
||||
//The whole document will be shown to the user and the user will draw their signature on the touch screen
|
||||
let signature = consentDocument.signatures!.first!
|
||||
let reviewConsentStep = ORKConsentReviewStep(identifier: "ConsentReviewStep", signature: signature, in: consentDocument)
|
||||
reviewConsentStep.text = "Revise el formulario de consentimiento."
|
||||
reviewConsentStep.reasonForConsent = "Consentimiento para unirse al estudio"
|
||||
steps += [reviewConsentStep]
|
||||
|
||||
//Passcode/TouchID Protection
|
||||
//Because it is personal data
|
||||
let passcodeStep = ORKPasscodeStep(identifier: "Passcode")
|
||||
passcodeStep.text = "Ahora crea un código de acceso a la app para proteger tu información."
|
||||
steps += [passcodeStep]
|
||||
|
||||
//Completion
|
||||
//A thank you message
|
||||
let completionStep = ORKCompletionStep(identifier: "CompletionStep")
|
||||
completionStep.title = "Bienvenido a bordo!"
|
||||
completionStep.text = "Muchas gracias por formar parte del estudio y poner tu granito de arena para contribuir a la mejora de la salud mental."
|
||||
steps += [completionStep]
|
||||
|
||||
return ORKOrderedTask(identifier: "ConsentTask", steps: steps)
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
//
|
||||
// ConsentViewController.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 10/10/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import ResearchKitUI
|
||||
import GoogleSignIn
|
||||
import FirebaseCore
|
||||
import FirebaseAuth
|
||||
import FirebaseStorage
|
||||
|
||||
class ConsentViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var joinButton: UIView!
|
||||
@IBOutlet weak var googleButton: UIButton!
|
||||
|
||||
private var status: Bool = false
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
// observar la notificación para saber cuando el usuario ha sido cargado
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userLoaded), name: Notification.Name("UserLoaded"), object: nil)
|
||||
|
||||
}
|
||||
|
||||
@objc func userLoaded() {
|
||||
print("\thasAccepted: \(UserManager.shared.user.hasAccepted)")
|
||||
// verificar si ya ha aceptado con firestore
|
||||
if UserManager.shared.user.hasAccepted {
|
||||
DispatchQueue.main.async {
|
||||
self.performSegue(withIdentifier: "unwindToTasks", sender: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
// eliminar el observador para evitar filtraciones de memoria
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
@IBAction func googleButtonTapped(_ sender: UIButton) {
|
||||
// codigo de inicio de sesion con google
|
||||
GIDSignIn.sharedInstance.signIn(withPresenting: self) { result, error in
|
||||
if let error = error {
|
||||
print("Error al iniciar sesión con Google: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
// El inicio de sesión fue exitoso
|
||||
guard let result = result else { return }
|
||||
let user = result.user
|
||||
let idToken = user.idToken?.tokenString ?? ""
|
||||
let accessToken = user.accessToken.tokenString
|
||||
|
||||
// Autenticación con Firebase
|
||||
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
|
||||
|
||||
Auth.auth().signIn(with: credential) { authResult, error in
|
||||
if let error = error {
|
||||
print("Error al autenticar con Firebase: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
// Usuario autenticado con éxito
|
||||
print("Usuario autenticado en Firebase: \(authResult?.user.displayName ?? "")")
|
||||
}
|
||||
}
|
||||
|
||||
// habilitar boton de inicio de sesion
|
||||
self.status = true
|
||||
showAlert(title: "Autenticación", message: "Sesión iniciada correctamente.")
|
||||
|
||||
}
|
||||
|
||||
@IBAction func joinButtonTapped(_ sender: UIButton) {
|
||||
if status == true {
|
||||
let taskViewController = ORKTaskViewController(task: ConsentTask, taskRun: nil)
|
||||
taskViewController.delegate = self
|
||||
present(taskViewController, animated: true, completion: nil)
|
||||
} else {
|
||||
showAlert(title: "Alerta", message: "Para unirse al estudio, primero debe iniciar sesión.")
|
||||
}
|
||||
}
|
||||
|
||||
func showAlert(title: String, message: String) {
|
||||
// Crear el controlador de alerta
|
||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
|
||||
// Añadir una acción (botón) al controlador de alerta
|
||||
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
||||
print("El usuario presionó OK")
|
||||
}
|
||||
alertController.addAction(okAction)
|
||||
|
||||
// Mostrar la alerta
|
||||
present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ConsentViewController: ORKTaskViewControllerDelegate{
|
||||
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) {
|
||||
switch reason {
|
||||
case .completed:
|
||||
// imprimir el documento de consentimiento
|
||||
let signatureResult: ORKConsentSignatureResult = taskViewController.result.stepResult(forStepIdentifier: "ConsentReviewStep")?.firstResult as! ORKConsentSignatureResult
|
||||
let consentDocument = ConsentDocument.copy() as! ORKConsentDocument
|
||||
signatureResult.apply(to: consentDocument)
|
||||
consentDocument.makePDF{ (data, error) -> Void in
|
||||
guard let pdfData = data else {
|
||||
print("No se generó el pdf corectamente")
|
||||
return
|
||||
}
|
||||
print("Tamaño del PDF: \(pdfData.count) bytes") // Solo para confirmar que el archivo se ha generado
|
||||
|
||||
var docURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last
|
||||
docURL = docURL?.appendingPathComponent("consent.pdf")
|
||||
try? data?.write(to: docURL!, options: .atomicWrite)
|
||||
guard let currentUser = Auth.auth().currentUser else { return }
|
||||
UserManager.shared.user.uid = currentUser.uid
|
||||
StorageManager.shared.uploadPDFConsent(fileURL: docURL!)
|
||||
}
|
||||
// poner valor de aceptado
|
||||
UserManager.shared.user.hasAccepted = true
|
||||
// segue
|
||||
performSegue(withIdentifier: "unwindToTasks", sender: nil)
|
||||
case .discarded, .failed, .saved:
|
||||
dismiss(animated: true, completion: nil)
|
||||
case .earlyTermination:
|
||||
print("\tError en early termination")
|
||||
@unknown default:
|
||||
print("\tError en default")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func taskViewController(_ taskViewController: ORKTaskViewController, viewControllerFor step: ORKStep) -> ORKStepViewController? {
|
||||
if step is HealthDataAuthStep {
|
||||
let healthStepViewController = HealthDataAuthStepViewController(step: step)
|
||||
return healthStepViewController
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
//
|
||||
// HealthDataAuthstep.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 11/10/24.
|
||||
//
|
||||
|
||||
import HealthKit
|
||||
import ResearchKit
|
||||
|
||||
class HealthDataAuthStep: ORKActiveStep {
|
||||
|
||||
let healthKitStore = HKHealthStore()
|
||||
|
||||
let healthDataItemsToRead: Set<HKObjectType> = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!]
|
||||
let healthDataItemsToWrite: Set<HKSampleType> = []
|
||||
|
||||
override init(identifier: String) {
|
||||
super.init(identifier: identifier)
|
||||
|
||||
title = NSLocalizedString("Datos de salud", comment: "")
|
||||
text = NSLocalizedString("A continuación se le pedirá que otorgue acceso para leer los datos de su frecuencia cardíaca para este estudio.\n\nSi rechazó la autorización anteriormente y desea otorgar el acceso, dirijase a \n\nConfiguración -> Privacidad -> Salud\n\n para autorizar la app.", comment: "")
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func getHealthAuthorization(_ completion: @escaping (_ success: Bool, _ error: NSError?) -> Void) {
|
||||
HKHealthStore().requestAuthorization(toShare: healthDataItemsToWrite, read: healthDataItemsToRead){ (success, error) -> Void in
|
||||
completion(success, error as NSError?)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
//
|
||||
// HealthDataAuthStepViewController.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 11/10/24.
|
||||
//
|
||||
|
||||
import HealthKit
|
||||
import ResearchKitActiveTask
|
||||
|
||||
class HealthDataAuthStepViewController: ORKActiveStepViewController {
|
||||
|
||||
var healthDataStep: HealthDataAuthStep? {
|
||||
return step as? HealthDataAuthStep
|
||||
}
|
||||
|
||||
override func goForward() {
|
||||
healthDataStep?.getHealthAuthorization(){succeeded, _ in
|
||||
guard succeeded || (TARGET_OS_SIMULATOR != 0) else { return }
|
||||
OperationQueue.main.addOperation {
|
||||
super.goForward()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,592 @@
|
||||
//
|
||||
// MMSETask.swift
|
||||
// App-RK-BM
|
||||
//
|
||||
// Created by Luis Mora on 17/10/24.
|
||||
//
|
||||
|
||||
import ResearchKit
|
||||
|
||||
class FormTask {
|
||||
static let shared = FormTask()
|
||||
|
||||
private init() {}
|
||||
|
||||
func createMMSETask() -> ORKOrderedTask {
|
||||
|
||||
var steps = [ORKStep]()
|
||||
|
||||
let textLetterAnswerFormat = ORKTextAnswerFormat(maximumLength: 200)
|
||||
textLetterAnswerFormat.multipleLines = true
|
||||
let textAnswerFormat = ORKTextAnswerFormat(maximumLength: 40)
|
||||
textAnswerFormat.multipleLines = false
|
||||
let numericAnswerFormat = ORKNumericAnswerFormat(style: .integer)
|
||||
|
||||
// Instrucciones
|
||||
let instructionStep = ORKInstructionStep(identifier: "instruction")
|
||||
instructionStep.title = "Mini examen del estado mental (MMSE)"
|
||||
instructionStep.text = "El presente formulario es de una prueba que evalua las funciones congnitivas. Por favor tomese su tiempo y complete este formulario en un lugar libre de distracciones."
|
||||
steps.append(instructionStep)
|
||||
|
||||
// Orientación del tiempo y del espacio
|
||||
let orientationInstructionsStep = ORKInstructionStep(identifier: "orientationInstructions")
|
||||
orientationInstructionsStep.title = "Orientación en el tiempo y el espacio"
|
||||
orientationInstructionsStep.text = "Las siguientes preguntas son de evaluación de la orientación del tiempo y del espacio."
|
||||
steps.append(orientationInstructionsStep)
|
||||
|
||||
|
||||
let timeOrientationForm = ORKFormStep(identifier: "timeOrientationForm", title: "Orientación del tiempo", text: "De información de la fecha actual")
|
||||
|
||||
// Pregunta 1
|
||||
let timeQuestionYear = ORKFormItem(identifier: "timeQuestionYear", text: "¿En qué año estamos?", answerFormat: textAnswerFormat)
|
||||
// Pregunta 2
|
||||
let timeQuestionMonth = ORKFormItem(identifier: "timeQuestionMonth", text: "¿En qué mes?", answerFormat: textAnswerFormat)
|
||||
// Pregunta 3
|
||||
let timeQuestionDay = ORKFormItem(identifier: "timeQuestionDay", text: "¿Cuál es el número del dia de hoy?", answerFormat: textAnswerFormat)
|
||||
// Pregunta 4
|
||||
let timeQuestionWeekDay = ORKFormItem(identifier: "timeQuestionWeekDay", text: "¿Qué dia de la semana es?", answerFormat: textAnswerFormat)
|
||||
|
||||
timeOrientationForm.formItems = [timeQuestionYear, timeQuestionMonth,timeQuestionDay, timeQuestionWeekDay]
|
||||
steps.append(timeOrientationForm)
|
||||
|
||||
|
||||
let spaceOrientationForm = ORKFormStep(identifier: "spaceOrientationForm", title: "Orientación del espacio", text: "De información del lugar donde se encuentra ahora.")
|
||||
|
||||
// pregunta 5
|
||||
let spaceQuestionCountry = ORKFormItem(identifier: "spaceQuestionCountry", text: "¿En qué país se encuentra?", answerFormat: textAnswerFormat)
|
||||
// pregunta 6
|
||||
let spaceQuestionArea = ORKFormItem(identifier: "spaceQuestionArea", text: "¿En qué estado?", answerFormat: textAnswerFormat)
|
||||
// pregunta 7
|
||||
let spaceQuestionLocality = ORKFormItem(identifier: "spaceQuestionLocality", text: "¿En que ciudad o localidad?", answerFormat: textAnswerFormat)
|
||||
|
||||
spaceOrientationForm.formItems = [spaceQuestionCountry, spaceQuestionArea, spaceQuestionLocality]
|
||||
steps.append(spaceOrientationForm)
|
||||
|
||||
// fijación?
|
||||
|
||||
// concentración y cálculo
|
||||
let concentrationInstructionsStep = ORKInstructionStep(identifier: "concentrationInstructions")
|
||||
concentrationInstructionsStep.title = "Concentración y cálculo"
|
||||
concentrationInstructionsStep.text = "Ahora se ha de evalular su capacidad de concentración al realizar unas operaciones matemáticas"
|
||||
steps.append(concentrationInstructionsStep)
|
||||
|
||||
let concentrationForm = ORKFormStep(identifier: "concentrationForm", title: "Concentración y cálculo", text: "Comenzemos desde el número 100, usted tiene que ir dando el resultado de dicha operación.")
|
||||
|
||||
let concentrationQuestionOne = ORKFormItem(identifier: "concentrationQuestionOne", text: "100 menos 7", answerFormat: numericAnswerFormat)
|
||||
|
||||
let concentrationQuestionTwo = ORKFormItem(identifier: "concentrationQuestionTwo", text: "Menos 7", answerFormat: numericAnswerFormat)
|
||||
|
||||
let concentrationQuestionThree = ORKFormItem(identifier: "concentrationQuestionThree", text: "Menos 7", answerFormat: numericAnswerFormat)
|
||||
|
||||
let concentrationQuestionFour = ORKFormItem(identifier: "concentrationQuestionFour", text: "Menos 7", answerFormat: numericAnswerFormat)
|
||||
|
||||
let concentrationQuestionFive = ORKFormItem(identifier: "concentrationQuestionFive", text: "Menos 7", answerFormat: numericAnswerFormat)
|
||||
|
||||
concentrationForm.formItems = [concentrationQuestionOne, concentrationQuestionTwo, concentrationQuestionThree, concentrationQuestionFour, concentrationQuestionFive]
|
||||
steps.append(concentrationForm)
|
||||
|
||||
// lenguaje y construcción
|
||||
let languajeInstructionsStep = ORKInstructionStep(identifier: "languajeInstructions")
|
||||
languajeInstructionsStep.title = "Lenguaje y construcción"
|
||||
languajeInstructionsStep.text = "Ahora se va a evaluar su capacidad de lenguaje, construcción y reconocimiento de objetos."
|
||||
steps.append(languajeInstructionsStep)
|
||||
|
||||
let watchImageInstructionStep = ORKInstructionStep(identifier: "watchImageInstructionStep")
|
||||
watchImageInstructionStep.title = "Lenguaje y construcción"
|
||||
watchImageInstructionStep.text = "Observa la imagen"
|
||||
watchImageInstructionStep.image = UIImage(named: "Watch")
|
||||
watchImageInstructionStep.imageContentMode = .scaleAspectFit
|
||||
steps.append(watchImageInstructionStep)
|
||||
|
||||
let watchImageQuestion = ORKQuestionStep(identifier: "watchImageQuestion", title: "Lenguaje y construcción", question: "Cuál es el nombre del objeto de la imagen?", answer: textAnswerFormat)
|
||||
steps.append(watchImageQuestion)
|
||||
|
||||
let pencilImageInstructionStep = ORKInstructionStep(identifier: "pencilImageInstructionStep")
|
||||
pencilImageInstructionStep.title = "Lenguaje y construcción"
|
||||
pencilImageInstructionStep.text = "Observa la imagen"
|
||||
pencilImageInstructionStep.image = UIImage(named: "Pencil")
|
||||
pencilImageInstructionStep.imageContentMode = .scaleAspectFit
|
||||
steps.append(pencilImageInstructionStep)
|
||||
|
||||
let pencilImageQuestion = ORKQuestionStep(identifier: "pencilImageQuestion", title: "Lenguaje y construcción", question: "Cuál es el nombre del objeto de la imagen?", answer: textAnswerFormat)
|
||||
steps.append(pencilImageQuestion)
|
||||
|
||||
let letterQuestion = ORKQuestionStep(identifier: "letterQuestion", title: "Lenguaje y construcción", question: "Escriba una frase como si estuviera contando algo en una carta.", answer: textLetterAnswerFormat)
|
||||
steps.append(letterQuestion)
|
||||
|
||||
// dibujo
|
||||
//let drawInstructionsStep = ORKInstructionStep(identifier: "drawInstrctionsStep")
|
||||
//drawInstructionsStep.title = "Lenguaje y construcción"
|
||||
//drawInstructionsStep.text = "A continuación, se te mostrará una figura. Por favor, cópiala en el área de dibujo a continuación."
|
||||
//drawInstructionsStep.image = UIImage(named: "Geometric Draw")
|
||||
//drawInstructionsStep.imageContentMode = .scaleAspectFit
|
||||
//steps.append(drawInstructionsStep)
|
||||
|
||||
//let drawingStep = ORKFormStep(identifier: "drawingStep", title: "Lenguaje y construcción", text: "Dibuja la figura aquí:")
|
||||
//drawingStep.formItems = [ORKFormItem(identifier: "drawingItem", text: nil, answerFormat: ORKAnswerFormat.imageAnswerFormat())]
|
||||
//steps.append(drawingStep)
|
||||
|
||||
// memoria
|
||||
let memoryQuestion = ORKQuestionStep(identifier: "memoryQuestion", title: "Memoria", question: "Recuerda el ultimo resultado que le di o en la sección de Concentración y cálculo? Escribalo.", answer: textAnswerFormat)
|
||||
steps.append(memoryQuestion)
|
||||
|
||||
// Fin de la prueba
|
||||
let completionStep = ORKCompletionStep(identifier: "completion")
|
||||
completionStep.title = "Fin de la prueba!"
|
||||
completionStep.text = "Gracias por completar el formulario."
|
||||
steps.append(completionStep)
|
||||
|
||||
return ORKOrderedTask(identifier: "MMSE", steps: steps)
|
||||
}
|
||||
|
||||
func createIPAQTask() -> ORKOrderedTask {
|
||||
|
||||
var steps = [ORKStep]()
|
||||
|
||||
// formatos de respuesta
|
||||
let scaleDaysWeekFormat = ORKScaleAnswerFormat(maximumValue: 7, minimumValue: 0, defaultValue: 0, step: 1)
|
||||
let scaleMinutesDayAnswerFormat = ORKNumericAnswerFormat(style: .integer, unit: "min", minimum: 0, maximum: 300)
|
||||
|
||||
|
||||
// Instrucciones iniciales
|
||||
let formInstructionsStep = ORKInstructionStep(identifier: "formInstructions")
|
||||
formInstructionsStep.title = "Cuestionario Internacional de Actividad Física - IPAQ"
|
||||
formInstructionsStep.text = "El presente formulario es un cuestionario que evalua su nivel de actividad física en base a la version corta del cuestionario internacional IPAQ. Por favor llenelo con total honestidad. La información ingresada es completamente privada."
|
||||
formInstructionsStep.image = UIImage(systemName: "figure.walk")
|
||||
formInstructionsStep.imageContentMode = .scaleAspectFit
|
||||
steps.append(formInstructionsStep)
|
||||
|
||||
// instrucciones de las actividades vigorosas
|
||||
let vigorousQuestionsInstructionsStep = ORKInstructionStep(identifier: "vigorousQuestionsInstructions")
|
||||
vigorousQuestionsInstructionsStep.title = "Actividades vigorosas"
|
||||
vigorousQuestionsInstructionsStep.text = "Piense en todas las actividades VIGOROSAS que usted realizó en los últimos 7 días. Las actividades físicas intensas se refieren a aquellas que implican un esfuerzo físico intenso y que lo hacen respirar mucho más intensamente que lo normal. Piense sólo en aquellas actividades físicas que realizó durante por lo menos 10 minutos seguidos"
|
||||
steps.append(vigorousQuestionsInstructionsStep)
|
||||
|
||||
// actividad vigorosa
|
||||
let firstQuestionStep = ORKQuestionStep(identifier: "firstQuestionVigorous", title: "Actividades Vigorosas", question: "1. Durante los últimos 7 días ¿En cuántos realizo actividades físicas vigorosas tales como levantar pesos pesados, cavar, hacer ejercicios aeróbicos o andar rápido en bicicleta?", answer: scaleDaysWeekFormat)
|
||||
firstQuestionStep.isOptional = false
|
||||
steps.append(firstQuestionStep)
|
||||
|
||||
let secondQuestionStep = ORKQuestionStep(identifier: "secondQuestionVigorous", title: "Actividades vigorosas", question: "2. Habitualmente, ¿Cuánto tiempo en total en minutos dedicó a una actividad física intensa en un dia promedio? (ejemplo: si practicó 1 hora y 30 minutos ingrese 90 minutos)", answer: scaleMinutesDayAnswerFormat)
|
||||
secondQuestionStep.isOptional = false
|
||||
steps.append(secondQuestionStep)
|
||||
|
||||
// actividad moderada
|
||||
|
||||
// instrucciones de las acitividades moderadas
|
||||
let moderateQuestionsInstructionsStep = ORKInstructionStep(identifier: "moderateQuestionsInstructions")
|
||||
moderateQuestionsInstructionsStep.title = "Actividades moderadas"
|
||||
moderateQuestionsInstructionsStep.text = "Piense en todas las actividades MODERADAS que usted realizó en los últimos 7 días. Las actividades moderadas son aquellas que requieren un esfuerzo físico moderado que lo hace respirar algo más intensamente que lo normal. Piense solo en aquellas actividades que realizó durante por lo menos 10 minutos seguidos."
|
||||
steps.append(moderateQuestionsInstructionsStep)
|
||||
|
||||
let thirdQuestionStep = ORKQuestionStep(identifier: "thirdQuestionModerate", title: "Actividades moderadas", question: "3. Durante los últimos 7 días, ¿En cuántos días hizo actividades físicas moderadas como transportar pesos livianos, andar en bicicleta a velocidad regular o jugar a dobles en tenis? No incluya caminar.", answer: scaleDaysWeekFormat)
|
||||
thirdQuestionStep.isOptional = false
|
||||
steps.append(thirdQuestionStep)
|
||||
|
||||
let fourthQuestionStep = ORKQuestionStep(identifier: "fourthQuestionModerate", title: "Actividades moderadas", question: "4. Habitualmente, ¿Cuánto tiempo en total en minutos dedicó a una actividad física moderada en un dia promedio? (ejemplo: si practicó 1 hora y 30 minutos ingrese 90 minutos)", answer: scaleMinutesDayAnswerFormat)
|
||||
fourthQuestionStep.isOptional = false
|
||||
steps.append(fourthQuestionStep)
|
||||
|
||||
// caminar
|
||||
let walkQuestionsInstructionsStep = ORKInstructionStep(identifier: "walkQuestionsInstructions")
|
||||
walkQuestionsInstructionsStep.title = "Caminar"
|
||||
walkQuestionsInstructionsStep.text = "Piense en el tiempo que usted dedicó a CAMINAR en los últimos 7 días. Esto incluye caminar en el trabajo o en la casa, para trasladarse de un lugar a otro, o cualquier otra caminata que usted podría hacer solamente para la recreación, el deporte, el ejercicio o el ocio."
|
||||
steps.append(walkQuestionsInstructionsStep)
|
||||
|
||||
let fifthQuestionStep = ORKQuestionStep(identifier: "fifthQuestionWalk", title: "Caminar", question: "5. Durante los últimos 7 días, ¿En cuántos caminó por lo menos 10 minutos seguidos?", answer: scaleDaysWeekFormat)
|
||||
fifthQuestionStep.isOptional = false
|
||||
steps.append(fifthQuestionStep)
|
||||
|
||||
let sixthQuestionStep = ORKQuestionStep(identifier: "sixthQuestionWalk", title: "Caminar", question: "6. Habitualmente, ¿Cuánto tiempo en total en minutos dedicó a caminar en uno de esos días en promedio?", answer: scaleMinutesDayAnswerFormat)
|
||||
sixthQuestionStep.isOptional = false
|
||||
steps.append(sixthQuestionStep)
|
||||
|
||||
// sedentarismo
|
||||
let sedentaryInstructionsStep = ORKInstructionStep(identifier: "sedentaryQuestionsInstructions")
|
||||
sedentaryInstructionsStep.title = "Sedentarismo"
|
||||
sedentaryInstructionsStep.text = "La ultima pregunta es acerca del tiempo que pasó usted SENTADO durante los días hábiles de los últimos 7 dias. Esto incluye el tiempo dedicado al trabajo, en la casa, en una clase, y durante el tiempo libre. Puede incluir el tiempo que paso sentado ante un escritorio, leyendo, viajando en autobús, o sentado o recostado mirando tele."
|
||||
steps.append(sedentaryInstructionsStep)
|
||||
|
||||
let scaleSedentaryQuestion = ORKNumericAnswerFormat(style: .integer, unit: "min", minimum: 0, maximum: 480)
|
||||
let seventhQuestionStep = ORKQuestionStep(identifier: "seventhQuestionSedentary", title: "Sedentarismo", question: "7. Habitualmente, ¿Cuánto tiempo en minutos pasó sentado durante un día hábil?", answer: scaleSedentaryQuestion)
|
||||
seventhQuestionStep.isOptional = false
|
||||
steps.append(seventhQuestionStep)
|
||||
|
||||
//Fin de la prueba
|
||||
let completionStep = ORKCompletionStep(identifier: "completion")
|
||||
completionStep.title = "Fin del formulario!"
|
||||
completionStep.text = "Gracias por completar el formulario"
|
||||
steps.append(completionStep)
|
||||
|
||||
return ORKOrderedTask(identifier: "IPAQ", steps: steps)
|
||||
}
|
||||
|
||||
func createMedicalRecordTask() -> ORKOrderedTask {
|
||||
var steps = [ORKStep]()
|
||||
|
||||
let textAnswerFormat = ORKTextAnswerFormat(maximumLength: 80)
|
||||
|
||||
// Instrucciones iniciales
|
||||
let formInstructionsStep = ORKInstructionStep(identifier: "instructions")
|
||||
formInstructionsStep.title = "Cuestionario de historial médico."
|
||||
formInstructionsStep.text = "El presente cuestionario tiene como objetivo conocer sus capacidades físicas y reunir información revelante para el estudio. Toda la información que ingrese es privada y solo será utilizada para el mismo."
|
||||
formInstructionsStep.image = UIImage(systemName: "heart.fill")
|
||||
formInstructionsStep.imageContentMode = .scaleAspectFit
|
||||
steps.append(formInstructionsStep)
|
||||
|
||||
let personalInfoStep = ORKInstructionStep(identifier: "personalInfoStep")
|
||||
personalInfoStep.title = "Información personal"
|
||||
personalInfoStep.text = "Acontinuación se te pedirá datos de tu información personal"
|
||||
personalInfoStep.image = UIImage(systemName: "person.fill")
|
||||
personalInfoStep.imageContentMode = .scaleAspectFit
|
||||
steps.append(personalInfoStep)
|
||||
|
||||
let nameQuestionStep = ORKQuestionStep(identifier: "nameQuestion", title: "Nombre", question: "¿Cuál es tu nombre?", answer: textAnswerFormat)
|
||||
steps.append(nameQuestionStep)
|
||||
|
||||
let gendChoices = [
|
||||
ORKTextChoice(text: "Hombre", value: "male" as NSString),
|
||||
ORKTextChoice(text: "Mujer", value: "female" as NSString),
|
||||
ORKTextChoice(text: "Prefiero no decirlo", value: "Inf" as NSString)]
|
||||
let genderChoiceFormat = ORKTextChoiceAnswerFormat(style: .singleChoice, textChoices: gendChoices)
|
||||
let sexGenderQuestionStep = ORKQuestionStep(identifier: "sexQuestion", title: "¿Sexo", question: "¿Cuál es tu sexo?", answer: genderChoiceFormat)
|
||||
sexGenderQuestionStep.isOptional = false
|
||||
steps.append(sexGenderQuestionStep)
|
||||
|
||||
let ageQuestion = ORKFormItem(sectionTitle: "¿Cuántos años tienes?")
|
||||
let ageAnswerFormat = ORKNumericAnswerFormat(style: .integer, unit: nil, minimum: 16, maximum: 120)
|
||||
let ageQuestionFormItem = ORKFormItem(identifier: "ageQuestion", text: nil, answerFormat: ageAnswerFormat)
|
||||
ageQuestionFormItem.placeholder = "Presiona aquí"
|
||||
ageQuestionFormItem.isOptional = false
|
||||
let ageForm = ORKFormStep(identifier: "ageQuestion", title: "Edad", text: " ")
|
||||
ageForm.formItems = [ageQuestion, ageQuestionFormItem]
|
||||
ageForm.isOptional = false
|
||||
steps.append(ageForm)
|
||||
|
||||
// peso
|
||||
let weightChoices = (35...200).map { ORKTextChoice(text: "\($0) kg", value: "\($0) kg" as NSString) }
|
||||
let weightAnswerFormat = ORKValuePickerAnswerFormat(textChoices: weightChoices)
|
||||
let weightQuestionStep = ORKQuestionStep(identifier: "weightQuestion", title: "Peso", question: "¿Cuál es tu peso?", answer: weightAnswerFormat)
|
||||
weightQuestionStep.isOptional = false
|
||||
steps.append(weightQuestionStep)
|
||||
|
||||
// estatura
|
||||
let heightChoices = (130...210).map { ORKTextChoice(text: "\($0) cm", value: "\($0) cm" as NSString) }
|
||||
let heightAnswerFormat = ORKValuePickerAnswerFormat(textChoices: heightChoices)
|
||||
let heightQuestionStep = ORKQuestionStep(identifier: "heightQuestion", title: "Estatura", question: "¿Cuánto mides?", answer: heightAnswerFormat)
|
||||
heightQuestionStep.isOptional = false
|
||||
steps.append(heightQuestionStep)
|
||||
|
||||
// residencia
|
||||
let residenceChoice = "Residencia"
|
||||
let residenceChoices = [
|
||||
ORKTextChoice(text: "Urbana", value: "urban" as NSString),
|
||||
ORKTextChoice(text: "Sub-Urbana", value: "sub-urban" as NSString),
|
||||
ORKTextChoice(text: "Rural", value: "rural" as NSString)]
|
||||
let residenceChoiceFormat = ORKTextChoiceAnswerFormat(style: .singleChoice, textChoices: residenceChoices)
|
||||
let residenceQuestionStep = ORKQuestionStep(identifier: "residenceQuestion", title: residenceChoice,question: "¿En que tipo de zona vives?", answer: residenceChoiceFormat)
|
||||
residenceQuestionStep.isOptional = false
|
||||
steps.append(residenceQuestionStep)
|
||||
|
||||
// ocupación
|
||||
let occupationQuestionTitle = "Ocupación"
|
||||
let occupationChoices = [
|
||||
ORKTextChoice(text: "Estudiante", value: "student" as NSString),
|
||||
ORKTextChoice(text: "Empleado", value: "employee" as NSString),
|
||||
ORKTextChoice(text: "Emprendedor", value: "entrepreneur" as NSString),
|
||||
ORKTextChoice(text: "Desempleado", value: "unemployed" as NSString),
|
||||
ORKTextChoice(text: "Retirado", value: "retired" as NSString),
|
||||
ORKTextChoice(text: "Hogar", value: "home" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString)]
|
||||
let occupationChoiceFormat = ORKValuePickerAnswerFormat(textChoices: occupationChoices)
|
||||
let occupationQuestionStep = ORKQuestionStep(identifier: "occupationQuestion", title: occupationQuestionTitle, question: "¿A que te dedicas?", answer: occupationChoiceFormat)
|
||||
occupationQuestionStep.isOptional = false
|
||||
steps.append(occupationQuestionStep)
|
||||
|
||||
// información adicional de ocupación
|
||||
let specifyOccupationAnswerFormat = ORKTextAnswerFormat(maximumLength: 200)
|
||||
specifyOccupationAnswerFormat.multipleLines = true // Permitir múltiples líneas para detalles extensos
|
||||
let occupationFormStep = ORKFormStep(identifier: "07Ocupacion", title: "Ocupación", text: "Especifica si es necesario.")
|
||||
occupationFormStep.formItems = [
|
||||
ORKFormItem(identifier: "occupationQuestion2", text: nil, answerFormat: occupationChoiceFormat),
|
||||
ORKFormItem(identifier: "occupationQuestion3", text: nil, answerFormat: specifyOccupationAnswerFormat)
|
||||
]
|
||||
steps.append(occupationFormStep)
|
||||
|
||||
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-
|
||||
|
||||
// Heredo Familiares
|
||||
let hereditaryInfoStepTitle = "Historial Patológico Heredofamiliar"
|
||||
let hereditaryInfoStep = ORKInstructionStep(identifier: "hereditaryInfoStep")
|
||||
hereditaryInfoStep.title = hereditaryInfoStepTitle
|
||||
hereditaryInfoStep.text = "En esta área te preguntaremos acerca de las enfermedades que padecen en tu familia"
|
||||
hereditaryInfoStep.image = UIImage(systemName: "person.3.fill")
|
||||
hereditaryInfoStep.imageContentMode = .scaleAspectFit
|
||||
|
||||
// patologias hereditarias
|
||||
let patologyChoice = "Patologías"
|
||||
let patologyChoices = [
|
||||
ORKTextChoice(text: "Diabetes", value: "diabetes" as NSString),
|
||||
ORKTextChoice(text: "Cardiopatias", value: "cardiopatias" as NSString),
|
||||
ORKTextChoice(text: "Hipertensión", value: "hypertension" as NSString),
|
||||
ORKTextChoice(text: "Cáncer", value: "cancer" as NSString),
|
||||
ORKTextChoice(text: "Asma", value: "asthma" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString),
|
||||
ORKTextChoice(text: "Ninguna", value: "none" as NSString)
|
||||
]
|
||||
let patologyChoiceFormat = ORKTextChoiceAnswerFormat(style: .multipleChoice, textChoices: patologyChoices)
|
||||
let patologyQuestionStep = ORKQuestionStep(identifier: "pathologiesQuestion", title: patologyChoice, question: "Selecciona unicamente las patologías que tienen o hayan tenido tus padres o tus abuelos", answer: patologyChoiceFormat)
|
||||
patologyQuestionStep.isOptional = false
|
||||
steps.append(patologyQuestionStep)
|
||||
|
||||
// patologías mentales hereditarias
|
||||
let mentalPatologyChoice = "Patologías Mentales"
|
||||
let mentalPatologyChoices = [
|
||||
ORKTextChoice(text: "Bipolaridad", value: "bipolarity" as NSString),
|
||||
ORKTextChoice(text: "Autismo", value: "autism" as NSString),
|
||||
ORKTextChoice(text: "Depresión", value: "depression" as NSString),
|
||||
ORKTextChoice(text: "TDAH", value: "TDAH" as NSString),
|
||||
ORKTextChoice(text: "Ansiedad", value: "anxiety" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString),
|
||||
ORKTextChoice(text: "Ninguna", value: "none" as NSString)
|
||||
]
|
||||
let mentalPatologyChoiceFormat = ORKTextChoiceAnswerFormat(style: .multipleChoice, textChoices: mentalPatologyChoices)
|
||||
let mentalPatologyQuestionStep = ORKQuestionStep(identifier: "mentalPathologiesQuestion", title: mentalPatologyChoice, question: "Selecciona solo las patologías mentales que tienen o tuvieron tus familiares cercanos como padre, madre, abuelo o abuela.", answer: mentalPatologyChoiceFormat)
|
||||
mentalPatologyQuestionStep.isOptional = false
|
||||
steps.append(mentalPatologyQuestionStep)
|
||||
|
||||
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
// antecedentes personales patológicos
|
||||
let persInfoStepTitle = "Historial Patológico Personal"
|
||||
let persInfoStep = ORKInstructionStep(identifier: "persInfoStep")
|
||||
persInfoStep.title = persInfoStepTitle
|
||||
persInfoStep.text = "En esta sección te haremos preguntas con respecto a las enfermedades que has padecido o padeces"
|
||||
persInfoStep.image = UIImage(systemName: "person.crop.circle")
|
||||
persInfoStep.imageContentMode = .scaleAspectFit
|
||||
steps.append(persInfoStep)
|
||||
|
||||
// enfermedades propias
|
||||
let diseasesChoice = "Enfermedades"
|
||||
let diseasesChoices = [
|
||||
ORKTextChoice(text: "Diabetes", value: "diabetes" as NSString),
|
||||
ORKTextChoice(text: "Cardiopatías", value: "cardiopatias" as NSString),
|
||||
ORKTextChoice(text: "Hipertension", value: "hypertension" as NSString),
|
||||
ORKTextChoice(text: "Cáncer", value: "cancer" as NSString),
|
||||
ORKTextChoice(text: "Asma", value: "asthma" as NSString),
|
||||
ORKTextChoice(text: "Hepatitis", value: "hepatitis" as NSString),
|
||||
ORKTextChoice(text: "Covid", value: "covid" as NSString),
|
||||
ORKTextChoice(text: "Tuberculosis", value: "tuberculosis" as NSString),
|
||||
ORKTextChoice(text: "SIDA", value: "SIDA" as NSString),
|
||||
ORKTextChoice(text: "Bipolaridad", value: "bipolarity" as NSString),
|
||||
ORKTextChoice(text: "Autismo", value: "autism" as NSString),
|
||||
ORKTextChoice(text: "Depresión", value: "depression" as NSString),
|
||||
ORKTextChoice(text: "TDAH", value: "TDAH" as NSString),
|
||||
ORKTextChoice(text: "Ansiedad", value: "anxiety" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString),
|
||||
ORKTextChoice(text: "Ninguna", value: "none" as NSString)]
|
||||
|
||||
let diseasesChoiceFormat = ORKTextChoiceAnswerFormat(style: .multipleChoice, textChoices: diseasesChoices)
|
||||
let diseasesQuestionStep = ORKQuestionStep(identifier: "personalPathologiesQuestion", title: diseasesChoice, question: "Selecciona las enfermedades que has padecido o padeces.", answer: diseasesChoiceFormat)
|
||||
diseasesQuestionStep.isOptional = false
|
||||
steps.append(diseasesQuestionStep)
|
||||
|
||||
// vacunación
|
||||
let vaccinationQuestionTitle = "Vacunación"
|
||||
let vaccinationChoices = [
|
||||
ORKTextChoice(text: "Si", value: "yes" as NSString),
|
||||
ORKTextChoice(text: "No", value: "no" as NSString),
|
||||
ORKTextChoice(text: "No recuerdo", value: "dont remember" as NSString)
|
||||
]
|
||||
let vaccinationChoiceFormat = ORKValuePickerAnswerFormat(textChoices: vaccinationChoices)
|
||||
let vaccinationQuestionStep = ORKQuestionStep(identifier: "vaccinationQuestion", title: vaccinationQuestionTitle, question: "¿Tienes tu esquema de vacunación completo?", answer: vaccinationChoiceFormat)
|
||||
vaccinationQuestionStep.isOptional = false
|
||||
steps.append(vaccinationQuestionStep)
|
||||
|
||||
// bebe alcohol?
|
||||
let alcoholQuestionTitle = "Bebedor de Alcohol"
|
||||
let alcoholChoices = [
|
||||
ORKTextChoice(text: "No bebo alcohol", value: "no alcohol" as NSString),
|
||||
ORKTextChoice(text: "Bebedor Social", value: "social drinker" as NSString),
|
||||
ORKTextChoice(text: "Bebedor Ocasional", value: "occasional drinker" as NSString),
|
||||
ORKTextChoice(text: "Dependiente del Alcohol", value: "dependent drinker" as NSString)]
|
||||
let alcoholChoiceFormat = ORKValuePickerAnswerFormat(textChoices: alcoholChoices)
|
||||
let alcoholQuestionStep = ORKQuestionStep(identifier: "alcoholQuestion", title: alcoholQuestionTitle, question: "¿Que tipo de bebedor de alcohol eres?", answer: alcoholChoiceFormat)
|
||||
alcoholQuestionStep.isOptional = false
|
||||
steps.append(alcoholQuestionStep)
|
||||
|
||||
// tabaco
|
||||
let smokeQuestionTitle = "Fumador de Tabaco"
|
||||
let smokeChoices = [
|
||||
ORKTextChoice(text: "No fumador", value: "no smoker" as NSString),
|
||||
ORKTextChoice(text: "Fumador Social", value: "social smoker" as NSString),
|
||||
ORKTextChoice(text: "Fumador Ocasional", value: "occasional smoker" as NSString),
|
||||
ORKTextChoice(text: "Dependiente del tabaco", value: "dependent smoker" as NSString)
|
||||
]
|
||||
let smokeChoiceFormat = ORKValuePickerAnswerFormat(textChoices: smokeChoices)
|
||||
let smokeQuestionStep = ORKQuestionStep(identifier: "tobaccoQuestion", title: smokeQuestionTitle, question: "¿Que tipo de fumador eres?", answer: smokeChoiceFormat)
|
||||
smokeQuestionStep.isOptional = false
|
||||
steps.append(smokeQuestionStep)
|
||||
|
||||
// drogas
|
||||
// cambiar la pregunta a una menos comprometedora y aclaracion del uso de la informacion
|
||||
let drugsPatologyChoice = "Drogas"
|
||||
let drugsPatologyChoices = [
|
||||
ORKTextChoice(text: "No consumo drogas", value: "no drugs" as NSString),
|
||||
ORKTextChoice(text: "Marihuana", value: "marihuana" as NSString),
|
||||
ORKTextChoice(text: "Cocaina", value: "cocaine" as NSString),
|
||||
ORKTextChoice(text: "MDMA (Extasis)", value: "MDMA" as NSString),
|
||||
ORKTextChoice(text: "Heroina", value: "heroina" as NSString),
|
||||
ORKTextChoice(text: "Metanfetaminas", value: "methamphetamine" as NSString),
|
||||
ORKTextChoice(text: "LSD", value: "LSD" as NSString),
|
||||
ORKTextChoice(text: "Hongos Alucionógenos", value: "hallucinogenic mushrooms" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString)
|
||||
]
|
||||
let drugsPatologyChoiceFormat = ORKTextChoiceAnswerFormat(style: .multipleChoice, textChoices: drugsPatologyChoices)
|
||||
let drugsPatologyQuestionStep = ORKQuestionStep(identifier: "drugsQuestion", title: drugsPatologyChoice, question: "Selecciona la sustancia o sustancias que consumes", answer: drugsPatologyChoiceFormat)
|
||||
drugsPatologyQuestionStep.isOptional = false
|
||||
steps.append(drugsPatologyQuestionStep)
|
||||
|
||||
let frecuencyDrugsQuestionTitle = "Frecuencia de uso de sustancias"
|
||||
let frecuencyDrugsChoices = [
|
||||
ORKTextChoice(text: "Nunca", value: "never" as NSString),
|
||||
ORKTextChoice(text: "Poco", value: "bit" as NSString),
|
||||
ORKTextChoice(text: "Moderado", value: "moderate" as NSString),
|
||||
ORKTextChoice(text: "Mucho", value: "a lot" as NSString)
|
||||
]
|
||||
let frecuencyDrugsChoiceFormat = ORKValuePickerAnswerFormat(textChoices: frecuencyDrugsChoices)
|
||||
let frecuencyDrugsQuestionStep = ORKQuestionStep(identifier: "drugsFrecuencyQuestion", title: frecuencyDrugsQuestionTitle, question: "¿Con que frecuencia consumes dichas sustancias?", answer: frecuencyDrugsChoiceFormat)
|
||||
frecuencyDrugsQuestionStep.isOptional = false
|
||||
steps.append(frecuencyDrugsQuestionStep)
|
||||
|
||||
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
// Antecedentes no patológicos
|
||||
|
||||
let noPersInfoStepTitle = "Historial No Patológico"
|
||||
let noPersInfoStep = ORKInstructionStep(identifier: "NoPathological")
|
||||
noPersInfoStep.title = noPersInfoStepTitle
|
||||
noPersInfoStep.text = "En esta sección, encontrará preguntas sobre sus antecedentes no patológicos, como sus actividades diarias y hábitos personales."
|
||||
noPersInfoStep.image = UIImage(systemName: "book.fill")
|
||||
noPersInfoStep.imageContentMode = .scaleAspectFit
|
||||
steps.append(noPersInfoStep)
|
||||
|
||||
// pregunta de actividad física en escala de likert
|
||||
let scaleQuestionStepTitle = "Actividad Física"
|
||||
let scaleAnswerFormat = ORKScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0, step: 1)
|
||||
let scaleQuestionStep = ORKQuestionStep(identifier: "physicalActivityQuestion", title: scaleQuestionStepTitle,question: "¿Que tan activo eres?", answer: scaleAnswerFormat)
|
||||
scaleQuestionStep.isOptional = false
|
||||
steps.append(scaleQuestionStep)
|
||||
|
||||
// Tipo de actividad fisica que realizas.
|
||||
let multiChoiceQuestionStepTitle = "¿Que tipo de actividades físicas realizas?"
|
||||
let textChoices = [
|
||||
ORKTextChoice(text: "Caminar", value: "walk" as NSString),
|
||||
ORKTextChoice(text: "Correr", value: "run" as NSString),
|
||||
ORKTextChoice(text: "Nadar", value: "swim" as NSString),
|
||||
ORKTextChoice(text: "Ciclismo", value: "cycling" as NSString),
|
||||
ORKTextChoice(text: "Futbol", value: "soccer" as NSString),
|
||||
ORKTextChoice(text: "Basquetbol", value: "basketball" as NSString),
|
||||
ORKTextChoice(text: "Tenis", value: "tennis" as NSString),
|
||||
ORKTextChoice(text: "Beisbol", value: "baseball" as NSString),
|
||||
ORKTextChoice(text: "Golf", value: "golf" as NSString),
|
||||
ORKTextChoice(text: "Atletismo", value: "athletics" as NSString),
|
||||
ORKTextChoice(text: "Futbol Americano", value: "american soccer" as NSString),
|
||||
ORKTextChoice(text: "Artes Marciales", value: "martial arts" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString),
|
||||
ORKTextChoice(text: "Ninguno", value: "none" as NSString)
|
||||
]
|
||||
|
||||
let multiChoiceAnswerFormat = ORKTextChoiceAnswerFormat(style: .multipleChoice, textChoices: textChoices)
|
||||
let multiChoiceQuestionStep = ORKQuestionStep(identifier: "typePhysicalActivityQuestion", title: multiChoiceQuestionStepTitle, question: "Selecciona las actividades que hagas regularmente", answer: multiChoiceAnswerFormat)
|
||||
multiChoiceQuestionStep.isOptional = false
|
||||
steps.append(multiChoiceQuestionStep)
|
||||
|
||||
// hobbies
|
||||
let hobbieQuestionStepTitle = "¿Cuales son tus Hobbies?"
|
||||
let hobbietextChoices = [
|
||||
ORKTextChoice(text: "Leer", value: "read" as NSString),
|
||||
ORKTextChoice(text: "Ver Películas y Series", value: "watch movies and series" as NSString),
|
||||
ORKTextChoice(text: "Jugar videojuegos", value: "play videogames" as NSString),
|
||||
ORKTextChoice(text: "Cocinar y hornear", value: "cook" as NSString),
|
||||
ORKTextChoice(text: "Viajar", value: "travel" as NSString),
|
||||
ORKTextChoice(text: "Jardinería", value: "gardening" as NSString),
|
||||
ORKTextChoice(text: "Artes", value: "arts" as NSString),
|
||||
ORKTextChoice(text: "Musica", value: "music" as NSString),
|
||||
ORKTextChoice(text: "Fotografía", value: "photography" as NSString),
|
||||
ORKTextChoice(text: "Escribir", value: "write" as NSString),
|
||||
ORKTextChoice(text: "Bailar", value: "dance" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString),
|
||||
ORKTextChoice(text: "Ninguno", value: "none" as NSString)]
|
||||
let hobbieAnswerFormat = ORKTextChoiceAnswerFormat(style: .multipleChoice, textChoices: hobbietextChoices)
|
||||
let hobbieQuestionStep = ORKQuestionStep(identifier: "hobbieQuestion", title: hobbieQuestionStepTitle, question: "Selecciona los hobbies que hagas con regularidad", answer: hobbieAnswerFormat)
|
||||
hobbieQuestionStep.isOptional = false
|
||||
steps.append(hobbieQuestionStep)
|
||||
|
||||
// level of study
|
||||
let studyQuestionTitle = "Nivel de estudios"
|
||||
let studyChoices = [
|
||||
ORKTextChoice(text: "Primaria", value: "primaria" as NSString),
|
||||
ORKTextChoice(text: "Secundaria", value: "secundaria" as NSString),
|
||||
ORKTextChoice(text: "Preparatoria", value: "preparatoria" as NSString),
|
||||
ORKTextChoice(text: "Universidad", value: "univesidad" as NSString),
|
||||
ORKTextChoice(text: "Maestría", value: "maestría" as NSString),
|
||||
ORKTextChoice(text: "Doctorado", value: "doctorado" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString)
|
||||
]
|
||||
let studyChoiceFormat = ORKValuePickerAnswerFormat(textChoices: studyChoices)
|
||||
let studyQuestionStep = ORKQuestionStep(identifier: "studyQuestion", title: studyQuestionTitle, question: "¿Cuál es tu nivel de estudios?", answer: studyChoiceFormat)
|
||||
studyQuestionStep.isOptional = false
|
||||
steps.append(studyQuestionStep)
|
||||
|
||||
// transporte
|
||||
let transportationQuestionTitle = "Medios de transporte"
|
||||
let transportationChoices = [
|
||||
ORKTextChoice(text: "Trabajo desde casa", value: "telecommuting" as NSString),
|
||||
ORKTextChoice(text: "Carro", value: "car" as NSString),
|
||||
ORKTextChoice(text: "Transporte público (autobus, metro, tren, otro)", value: "public transport" as NSString),
|
||||
ORKTextChoice(text: "Bicicleta", value: "bicycle" as NSString),
|
||||
ORKTextChoice(text: "Motocicleta", value: "motorcycle" as NSString),
|
||||
ORKTextChoice(text: "Caminando", value: "walking" as NSString),
|
||||
ORKTextChoice(text: "Scooter electrica", value: "electricscooter" as NSString),
|
||||
ORKTextChoice(text: "Skateboard", value: "skateboard" as NSString),
|
||||
ORKTextChoice(text: "Otro", value: "other" as NSString)]
|
||||
let transportationChoiceFormat = ORKValuePickerAnswerFormat(textChoices: transportationChoices)
|
||||
let transportationQuestionStep = ORKQuestionStep(identifier: "transportationQuestion", title: transportationQuestionTitle, question: "¿Cuál es el medio de transporte que utiliza desde su casa al trabajo o escuela?", answer: transportationChoiceFormat)
|
||||
transportationQuestionStep.isOptional = false
|
||||
steps.append(transportationQuestionStep)
|
||||
|
||||
//Alimentación
|
||||
// cuantas veces comes
|
||||
let alimentationQuestionStepTitle = "Alimentación"
|
||||
let alimentationAnswerFormat = ORKScaleAnswerFormat(maximumValue: 10, minimumValue: 0, defaultValue: 0, step: 1)
|
||||
let feedingQuestionStep = ORKQuestionStep(identifier: "feedingQuestion", title: alimentationQuestionStepTitle,question: "¿Cuantas comidas haces por día?", answer: alimentationAnswerFormat)
|
||||
feedingQuestionStep.isOptional = false
|
||||
steps.append(feedingQuestionStep)
|
||||
|
||||
// tipo de alimento
|
||||
//modificar la pregunta
|
||||
let typeFoodChoice = "Tipos de comida"
|
||||
let typeFoodChoices = [
|
||||
ORKTextChoice(text: "Pollo", value: "chicken" as NSString),
|
||||
ORKTextChoice(text: "Res", value: "beef" as NSString),
|
||||
ORKTextChoice(text: "Cerdo", value: "pig" as NSString),
|
||||
ORKTextChoice(text: "Marina", value: "marina" as NSString),
|
||||
ORKTextChoice(text: "Frutas/Vegetales", value: "fruits/vegetables" as NSString),
|
||||
ORKTextChoice(text: "Otros", value: "other" as NSString)]
|
||||
let typeFoodChoiceFormat = ORKTextChoiceAnswerFormat(style: .multipleChoice, textChoices: typeFoodChoices)
|
||||
let typeFoodQuestionStep = ORKQuestionStep(identifier: "typeFoodQuestion", title: typeFoodChoice, question: "¿Que tipo de comida consumes?", answer: typeFoodChoiceFormat)
|
||||
typeFoodQuestionStep.isOptional = false
|
||||
steps.append(typeFoodQuestionStep)
|
||||
|
||||
//Fin de la prueba
|
||||
let completionStep = ORKCompletionStep(identifier: "completion")
|
||||
completionStep.title = "Fin del cuestionario!"
|
||||
completionStep.text = "Gracias por completar el cuestionario y por tomarte tu tiempo!"
|
||||
completionStep.iconImage = UIImage(systemName: "checkmark.seal")
|
||||
completionStep.detailText = "Historia clinica completa."
|
||||
steps.append(completionStep)
|
||||
|
||||
return ORKOrderedTask(identifier: "MedicalRecord", steps: steps)
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
//
|
||||
// FormsViewController.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 14/10/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import ResearchKitActiveTask
|
||||
|
||||
class FormsViewController: UIViewController, CLLocationManagerDelegate {
|
||||
|
||||
@IBOutlet weak var recordButton: UIButton!
|
||||
@IBOutlet weak var ipaqButton: UIButton!
|
||||
@IBOutlet weak var mmseButton: UIButton!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
title = "Formularios"
|
||||
|
||||
// solicitar permisos para usar la ubicación
|
||||
LocationUtility.shared.requestLocationAuthorization()
|
||||
|
||||
// observador para saber cuando los datos estan listos
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(locationDataIsReady), name: .locationReady, object: nil)
|
||||
|
||||
self.buttonsFormsManager()
|
||||
}
|
||||
|
||||
@objc func locationDataIsReady() {
|
||||
print("\t Los datos de ubicación están listos y almacenados en LocationUtility.")
|
||||
}
|
||||
|
||||
func buttonsFormsManager() {
|
||||
// habilitar o deshabilitar botones
|
||||
if UserManager.shared.user.madeIPAQ {
|
||||
self.ipaqButton.isEnabled = false
|
||||
} else {
|
||||
self.ipaqButton.isEnabled = true
|
||||
}
|
||||
if UserManager.shared.user.madeMMSE {
|
||||
self.mmseButton.isEnabled = false
|
||||
} else {
|
||||
self.mmseButton.isEnabled = true
|
||||
}
|
||||
if UserManager.shared.user.madeMR {
|
||||
self.recordButton.isEnabled = false
|
||||
} else {
|
||||
self.recordButton.isEnabled = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@IBAction func mmseButtonTapped(_ sender: UIButton) {
|
||||
let formTask = FormTask.shared.createMMSETask()
|
||||
let formTaskViewController = ORKTaskViewController(task: formTask, taskRun: nil)
|
||||
formTaskViewController.delegate = self
|
||||
present(formTaskViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func ipaqButtonTapped(_ sender: UIButton) {
|
||||
let formTask = FormTask.shared.createIPAQTask()
|
||||
let formTaskViewController = ORKTaskViewController(task: formTask, taskRun: nil)
|
||||
formTaskViewController.delegate = self
|
||||
present(formTaskViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func recordButtonTapped(_ sender: UIButton) {
|
||||
let formTask = FormTask.shared.createMedicalRecordTask()
|
||||
let formTaskViewController = ORKTaskViewController(task: formTask, taskRun: nil)
|
||||
formTaskViewController.delegate = self
|
||||
present(formTaskViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
@IBAction func backButtonTapped(_ sender: UIButton) {
|
||||
// seguir con el unwindSegue
|
||||
performSegue(withIdentifier: "unwindToTaskView", sender: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension FormsViewController: ORKTaskViewControllerDelegate {
|
||||
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: (any Error)?) {
|
||||
// cerrar la ventana
|
||||
taskViewController.dismiss(animated: true, completion: nil)
|
||||
|
||||
// verificar si hay un error
|
||||
if let error = error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
// obtener los resultados
|
||||
switch reason {
|
||||
case .completed:
|
||||
let result = taskViewController.result
|
||||
processResults(result: result)
|
||||
self.buttonsFormsManager()
|
||||
case .discarded, .failed, .earlyTermination, .saved:
|
||||
print("No se completo con exito")
|
||||
|
||||
@unknown default:
|
||||
print("No se completo con exito")
|
||||
}
|
||||
}
|
||||
|
||||
func processResults(result: ORKTaskResult) {
|
||||
var typeForm = ""
|
||||
var questionsList: [String] = []
|
||||
var answersList: [String] = []
|
||||
|
||||
if result.identifier == "MMSE" {
|
||||
typeForm = "MMSE"
|
||||
UserManager.shared.user.madeMMSE = true
|
||||
} else if result.identifier == "IPAQ" {
|
||||
typeForm = "IPAQ"
|
||||
UserManager.shared.user.madeIPAQ = true
|
||||
} else if result.identifier == "MedicalRecord" {
|
||||
typeForm = "MedicalRecord"
|
||||
UserManager.shared.user.madeMR = true
|
||||
}
|
||||
|
||||
// Desempaquetar result.results con guard let
|
||||
guard let stepResults = result.results else {
|
||||
print("No se encontraron resultados.")
|
||||
return
|
||||
}
|
||||
|
||||
// Iterar sobre los resultados desempaquetados
|
||||
for stepResult in stepResults {
|
||||
// Verificar si stepResult es de tipo ORKStepResult
|
||||
guard let stepResult = stepResult as? ORKStepResult else {
|
||||
print("El stepResult no es de tipo ORKStepResult.")
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterar sobre los resultados individuales dentro del ORKStepResult
|
||||
for result in stepResult.results ?? [] {
|
||||
// Verificar si result es de tipo ORKQuestionResult
|
||||
guard let questionResult = result as? ORKQuestionResult else {
|
||||
print("El resultado no es de tipo ORKQuestionResult.")
|
||||
continue
|
||||
}
|
||||
|
||||
// Desempaquetar la respuesta
|
||||
guard let answer = questionResult.answer else {
|
||||
print("Pregunta: \(questionResult.identifier), no se encontró respuesta.")
|
||||
continue
|
||||
}
|
||||
|
||||
// Imprimir pregunta y respuesta
|
||||
print("Pregunta: \(questionResult.identifier), Respuesta: \(answer)")
|
||||
|
||||
// agregar pregunta
|
||||
questionsList.append(questionResult.identifier)
|
||||
// agregar respuesta
|
||||
if let stringAnswer = answer as? String {
|
||||
answersList.append(stringAnswer)
|
||||
} else if let numericAnswer = answer as? NSNumber {
|
||||
answersList.append(numericAnswer.stringValue)
|
||||
} else if let arrayAnswer = answer as? [String] {
|
||||
answersList.append(arrayAnswer.joined(separator: ", "))
|
||||
} else {
|
||||
answersList.append("Tipo de respuesta desconocido")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StorageManager.shared.updateFormToFirestore(type: typeForm, questions: questionsList, answers: answersList)
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
# Controlador de la vista de formularios
|
||||
En esta sección del modelo MVC se encuentran dos archivos, _FormsViewController.swift_ y _FormTask.swift_.
|
||||
|
||||
## FormTask
|
||||
En este archivo se encuentran los formularios MMSE, IPAQ y la historia clinica dentro de la clase con el mismo nombre del archivo, misma que esta establecida como un singleton class, _static let shared = FormTask()_, para ser posible acceder a ella desde cualquier punto.
|
||||
|
||||
El modo de acceder a los metodos de la clase es el siguiente:
|
||||
* FormTask.shared.---
|
||||
|
||||
Los formularios son devueltos como un _ORKOrderedTask_ de ResearchKit, esto permite presentar las preguntas del formulario de una manera secuencial, ordenada y bien estructurada.
|
||||
La versión elegida para cada formulario se muestra a continuación y es importante ya que en base a ella y a su orden es como se muestran las preguntas.
|
||||
<img src="./src/img/MMSE.png", , alt="Formulario MMSE">
|
||||
<img src="./src/img/IPAQ.png", alt="Formulario IPAQ">
|
||||
|
||||
Las respuestas del usuario a cada pregunta se ven ligadas a la pregunta en la cual se registraron, se este modo son filtradas y subidas a Firebase
|
||||
|
||||
## Formulario MMSE
|
||||
Este es creado en la función _createMMSETask()_. en la siguiente tabla se relacionan las preguntas y los identificadores que les corresponden.
|
||||
| Pregunta | Identificador |
|
||||
|--------------|--------------|
|
||||
| ¿Qué dia de la semana es? | timeQuestionWeekDay |
|
||||
| ¿En qué año estamos? | timeQuestionYear |
|
||||
| ¿En qué mes? | timeQuestionMonth |
|
||||
| ¿Cuál es el número del dia de hoy? | timeQuestionDay |
|
||||
| ¿En qué pais se encuentra? | spaceQuestionCountry |
|
||||
| ¿En qué estado? | spaceQuestionArea |
|
||||
| ¿En que cuidad o localidad? | spaceQuestionLocality |
|
||||
| ¿100 menos 7? | concentrationQuestionOne |
|
||||
| ¿Menos 7? | concentrationQuestionTwo |
|
||||
| ¿Menos 7? | concentrationQuestionThree |
|
||||
| ¿Menos 7? | concentrationQuestionFour |
|
||||
| ¿Menos 7? | concentrationQuestionFive |
|
||||
| ¿Cuál es el nombre del objeto de la imagen (Reloj)? | watchImageQuestion |
|
||||
| ¿Cuál es el nombre del objeto de la imagen (boligráfo)? | pencilImageQuestion |
|
||||
| Escriba una frase como si estuviera contando algo en una carta | letterQuestion |
|
||||
| Recuerda el ultimo resultado que le di o en la sección de Concentración y cálculo? Escribalo | memoryQuestion |
|
||||
|
||||
Cabe decir que no todas las preguntas fueron incorporadas ya que de momento no hay forma de registrar requerida por el momento, como la pregunta de la sección "Fijación" y algunas otras de "Lenguaje y construcción".
|
||||
|
||||
Este formulario recibe el identificador "MMSE". Los identificadores tienen un papel importante ya que ayudan a filtrar e identificar las respuestas especificas del usuario a cada pregunta y formulario, la importancia de esto se ve mas a detalle en el archivo _FormsViewController_.
|
||||
|
||||
## Formulario IPAQ
|
||||
Este es creado en la función _createIPAQTask()_ y sigue la misma lógica que el formulario MMSE. Los identificadores de cada pregunta se muestran a continuación
|
||||
| Pregunta | Identificador |
|
||||
|----------|---------------|
|
||||
| 1. Durante los últimos 7 días ¿En cuántos realizo actividades físicas vigorosas tales como levantar pesos pesados, cavar, hacer ejercicios aeróbicos o andar rápido en bicicleta? | firstQuestionVigorous |
|
||||
| 2. Habitualmente, ¿Cuánto tiempo en total en minutos dedicó a una actividad física intensa en uno de esos días? (ejemplo: si practicó 1 hora y 20 minutos ingrese 80 minutos) | secondQuestionVigorous |
|
||||
| 3. Durante los últimos 7 días, ¿En cuántos días hizo actividades físicas moderadas como transportar pesos livianos, andar en bicicleta a velocidad regular o jugar a dobles en tenis? No incluya caminar. | thirdQuestionModerate |
|
||||
| 4. Habitualmente, ¿Cuánto tiempo en total en minutos dedicó a una actividad física moderada en uno de esos días? (ejemplo: si practicó 1 hora y 20 minutos ingrese 80 minutos) | fourthQuestionModerate |
|
||||
| 5. Durante los últimos 7 días, ¿En cuántos caminó por lo menos 10 minutos seguidos? | fifthQuestionWalk |
|
||||
| 6. Habitualmente, ¿Cuánto tiempo en total en minutos dedicó a caminar en uno de esos días? | sixthQuestionWalk |
|
||||
| 7. Habitualmente, ¿Cuánto tiempo en minutos pasó sentado durante un día hábil? | seventhQuestionSedentary |
|
||||
|
||||
Las respuesas a este formulario se dan en enteros, ello permite el cálculo de la clasificación de los niveles de actividad física, ya sea alto, moderado o bajo.
|
||||
|
||||
Finalmente este recibe el identificador "IPAQ"
|
||||
|
||||
## Hisroria Médica
|
||||
De igual modo, este sigue el mismo patrón que los anteriores formularios, siendo este el más extenso. Los identificadores a cada pregunta se muestran a continuación:
|
||||
|
||||
| Pregunta | Identificador |
|
||||
|----------|---------------|
|
||||
| ¿Cuál es tu nombre? | nameQuestion |
|
||||
| ¿Cuál es tu sexo? | sexQuestion |
|
||||
| Edad | ageQuestion |
|
||||
| ¿Cuál es tu peso? | weightQuestion |
|
||||
| ¿Cuánto mides? | heightQuestion |
|
||||
| ¿En qué tipo de zona vives? | residenceQuestion |
|
||||
| ¿A qué te dedicas? | occupationQuestion |
|
||||
| Ocupación (Especifica si es necesario) | OccupationQuestion3 |
|
||||
| Selecciona únicamente las patologías que tienen o hayan tenido tus padres o tus abuelos | pathologiesQuestion |
|
||||
| Selecciona solo las patologías mentales que tienen o tuvieron tus familiares cercanos como padre, madre, abuelo o abuela. | mentalPathologiesQuestion |
|
||||
| Selecciona las enfermedades que has padecido o padeces. | personalPathologiesQuestion |
|
||||
| ¿Tienes tu esquema de vacunación completo? | vaccinationQuestion |
|
||||
| ¿Qué tipo de bebedor de alcohol eres? | alcoholQuestion |
|
||||
| ¿Qué tipo de fumador eres? | tobaccoQuestion |
|
||||
| Selecciona la sustancia o sustancias que consumes | drugsQuestion |
|
||||
| ¿Con qué frecuencia consumes dichas sustancias? | drugsFrecuencyQuestion |
|
||||
| ¿Que tan activo eres? | physicalActivityQuestion |
|
||||
| ¿Que tipo de actividades físicas realizas? | typePhysicalActivityQuestion |
|
||||
| ¿Cuales son tus Hobbies? | hobbieQuestion |
|
||||
| ¿Cuál es tu nivel de estudios? | studyQuestion |
|
||||
| ¿Cuál es el medio de transporte que utiliza desde su casa al trabajo o escuela? | transportationQuestion |
|
||||
| ¿Cuantas comidas haces por día? | feedingQuestion |
|
||||
| ¿Que tipo de comida consumes? | typeFoodQuestion |
|
||||
|
||||
## FormsViewController
|
||||
Las principales funciones de este archivo es, en primer lugar, asegurarse que se los usuarios solo pueden llenar una unica vez cada formulario, esto se logra verificando los estados de la depencencia _UserManager_ en la función _buttonsFormsManager()_.
|
||||
Cuando cada boton es presionado, si es que se encuentra activo, se presenta una vista generada por ResearchKit en base al ORKOrderedTask correspondiente generada en _FormTask.swift_ como se menciono anteriormente, ResearchKit se encarga de la presentación visual de las preguntas.
|
||||
|
||||
En `extension FormsViewController: ORKTaskViewControllerDelegate` se encuentra el código donde se gestionan los resultados de las vistas generadas por ResearchKit antes mencionadas.
|
||||
El flujo de trabajo se esta sección es el siguiente:
|
||||
|
||||
* Primero se corrobora que la tarea haya sido completada con exito: `case .completed:`
|
||||
* Si ha sido completada la tarea del formulario, se envian los resultados de `taskViewController.result` a la función `processResults(result: result)`
|
||||
* Una vez en la función se verifica el identificador del ORKOrderedTask, como en el siguiente ejemplo ` if result.identifier == "MMSE" ` y se le da el valor a _typeForm_ en base a dicho identificador para saber cual formulario a sido completado, a su vez se cambia el valor de _UserManager_ indicando que se ha completado el formulario correspondiente.
|
||||
* Posteriormente, en el bucle for `for stepResult in stepResults` se recorre por todos los resultados generados para filtrar los que son se utilidad.
|
||||
* El filtro de dicho bucle _for_ es el siguiente: verificar que sea del tipo _ORKStepResult_ y luego si es del tipo _OrkQuestionResult_.
|
||||
* Posteeriormente se desempaqueta el resultado en `guard let answer = questionResult.answer` y se agrega el identificador del ORKQuestionStep a la lista _questionsList_.
|
||||
* Luego se agrega la respuesta, verificando primero que tipo de dato es, puede ser un string, un valor entero o un arrayChar, y se hace una conversion a String en caso que no lo sea, las respuestas se agregan a la lista _answersList_
|
||||
* Finalmente se manda el _typeForm_, _questionsList_ y _answersList_ a `StorageManager.shared.updateFormToFirestore` para subir la información a Firebase. El modo en el que esta se sube se puede ver en el readme de dicha carpeta.
|
||||
|
||||
Este ViewController solicita al usuario acceso a la ubicación GPS mediante el modulo _LocationUtility_ con el fin de obtener información necesaria para el formulario MMSE. Especificamente se solicita en `LocationUtility.shared.requestLocationAuthorization()`, para mas información de _LocationUtility_ favor de revisarlo en su carpeta en el README.md.
|
@ -0,0 +1,30 @@
|
||||
//
|
||||
// IntroSegue.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 10/10/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class IntroSegue: UIStoryboardSegue {
|
||||
|
||||
override func perform() {
|
||||
let controllerToReplace = source.children.first
|
||||
let destinationControllerView = destination.view
|
||||
|
||||
destinationControllerView?.translatesAutoresizingMaskIntoConstraints = true
|
||||
destinationControllerView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
destinationControllerView?.frame = source.view.bounds
|
||||
|
||||
controllerToReplace?.willMove(toParent: nil)
|
||||
source.addChild(destination)
|
||||
|
||||
source.view.addSubview(destinationControllerView!)
|
||||
controllerToReplace?.view.removeFromSuperview()
|
||||
|
||||
destination.didMove(toParent: source)
|
||||
controllerToReplace?.removeFromParent()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
//
|
||||
// IntroViewController.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 10/10/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import ResearchKitActiveTask
|
||||
|
||||
class IntroViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
if ORKPasscodeViewController.isPasscodeStoredInKeychain() {
|
||||
UserManager.shared.user.hasAccepted = true
|
||||
toTasks()
|
||||
} else {
|
||||
toConsent()
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
func toConsent() {
|
||||
performSegue(withIdentifier: "toConsent", sender: self)
|
||||
}
|
||||
|
||||
func toTasks() {
|
||||
performSegue(withIdentifier: "toTasks", sender: self)
|
||||
}
|
||||
|
||||
var contentHidden = false {
|
||||
didSet {
|
||||
guard contentHidden != oldValue && isViewLoaded else { return }
|
||||
children.first?.view.isHidden = contentHidden
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func unwindToTasks(_ segue: UIStoryboardSegue){
|
||||
toTasks()
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
//
|
||||
// TasksViewController.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 11/10/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import ResearchKitUI
|
||||
import GoogleSignIn
|
||||
import FirebaseAuth
|
||||
|
||||
class TasksViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var documentButton: UIButton!
|
||||
@IBOutlet weak var leaveButton: UIButton!
|
||||
@IBOutlet weak var activitiesButton: UIButton!
|
||||
@IBOutlet weak var formsButton: UIButton!
|
||||
@IBOutlet weak var userLabel: UILabel!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
if let user = Auth.auth().currentUser {
|
||||
if let name = user.displayName {
|
||||
self.userLabel.text = "Bienvenid@: \(name)"
|
||||
} else {
|
||||
print("El usuario no tiene un nombre asociado.")
|
||||
}
|
||||
UserManager.shared.user.uid = user.uid
|
||||
} else {
|
||||
print("No hay un usuario autenticado.")
|
||||
}
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
@IBAction func leaveButtonTapped(_ sender: UIButton) {
|
||||
showAlertSignOut()
|
||||
}
|
||||
|
||||
func showAlertSignOut() {
|
||||
let alert = UIAlertController(title: "Alerta", message: "Al salir del estudio tambien cierras sesión con Google, ¿Deseas continuar?", preferredStyle: .alert)
|
||||
let actionOk = UIAlertAction(title: "Continuar", style: .default) {_ in
|
||||
self.signOut()
|
||||
ORKPasscodeViewController.removePasscodeFromKeychain()
|
||||
self.performSegue(withIdentifier: "returnToConsent", sender: nil)
|
||||
}
|
||||
let actionCancel = UIAlertAction(title: "Cancelar", style: .cancel) {_ in
|
||||
print("Cancelar")
|
||||
}
|
||||
alert.addAction(actionOk)
|
||||
alert.addAction(actionCancel)
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func signOut() {
|
||||
// cerrar sesion en Firebase
|
||||
do {
|
||||
try Auth.auth().signOut()
|
||||
print("Sesion cerrada en Firebase")
|
||||
} catch let signOutError as NSError {
|
||||
print("Error al cerrar sesion en Firebase: \(signOutError)")
|
||||
}
|
||||
// cerrar sesion de google
|
||||
GIDSignIn.sharedInstance.signOut()
|
||||
print("Sesión cerrada en Google")
|
||||
}
|
||||
|
||||
@IBAction func documentButtonTapped(_ 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 = "Consentimiento del estudio"
|
||||
return ORKOrderedTask(identifier: String("ConsentPDF"), steps: [PDFViewerStep])
|
||||
}
|
||||
|
||||
func toForms() {
|
||||
performSegue(withIdentifier: "toForms", sender: self)
|
||||
}
|
||||
|
||||
func toActivities() {
|
||||
performSegue(withIdentifier: "toActivities", sender: self)
|
||||
}
|
||||
|
||||
@IBAction func formsButtonTapped(_ sender: UIButton) {
|
||||
toForms()
|
||||
}
|
||||
|
||||
@IBAction func activitiesButtonTapped(_ sender: UIButton) {
|
||||
toActivities()
|
||||
}
|
||||
|
||||
@IBAction func unwindToTaskView(_ sender: UIStoryboardSegue) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension TasksViewController: ORKTaskViewControllerDelegate{
|
||||
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskFinishReason, error: Error?) {
|
||||
taskViewController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
//
|
||||
// BluetoothManager.swift
|
||||
// App-RK-BM
|
||||
//
|
||||
// Created by Luis Mora on 28/10/24.
|
||||
//
|
||||
|
||||
import CoreBluetooth
|
||||
|
||||
protocol BluetoothManagerDelegate: AnyObject {
|
||||
func didUpdateConnectionStatus(isConnected: Bool)
|
||||
func didReceiveData(fromCharacteristic uuid: String, data: String)
|
||||
}
|
||||
|
||||
class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
|
||||
static let shared = BluetoothManager() // Singleton para un acceso fácil
|
||||
|
||||
// Define los UUIDs
|
||||
private let SERVICE_UUID = CBUUID(string: "4fafc201-1fb5-459e-8fcc-c5c9c331914b")
|
||||
private let CHARACTERISTIC_UUID_CMD = CBUUID(string: "beb5483e-36e1-4688-b7f5-ea07361b26a8")
|
||||
private let CHARACTERISTIC_UUID_MAX = CBUUID(string: "beb5483e-36e1-4688-b7f5-ea07361b26b1") // UUID ficticio para MAX3010
|
||||
private let CHARACTERISTIC_UUID_MPU = CBUUID(string: "beb5483e-36e1-4688-b7f5-ea07361b26b2") // UUID ficticio para MPU6050
|
||||
|
||||
private var centralManager: CBCentralManager!
|
||||
private var esp32Peripheral: CBPeripheral?
|
||||
private var characteristics: [CBUUID: CBCharacteristic] = [:] // variable para almacenar características encontradas
|
||||
|
||||
weak var delegate: BluetoothManagerDelegate? // delegado para notificaciones
|
||||
|
||||
override init () {
|
||||
super.init()
|
||||
self.centralManager = CBCentralManager(delegate: self, queue: nil)
|
||||
}
|
||||
|
||||
// inicia el escaneo de dispositivos
|
||||
func startScanning() {
|
||||
if centralManager.state == .poweredOn {
|
||||
centralManager.scanForPeripherals(withServices: [SERVICE_UUID], options: nil)
|
||||
centralManager.scanForPeripherals(withServices: nil, options: nil)
|
||||
print("Buscando ESP32...")
|
||||
}
|
||||
}
|
||||
|
||||
// detiene la conexión manualmente
|
||||
func disconnect() {
|
||||
if let esp32Peripheral = esp32Peripheral {
|
||||
centralManager.cancelPeripheralConnection(esp32Peripheral)
|
||||
print("Desconectando el ESP32...")
|
||||
}
|
||||
}
|
||||
|
||||
// CBCentralManagerDelegate - Estado de Bluetooth
|
||||
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
if central.state != .poweredOn {
|
||||
print("BT no esta disponible")
|
||||
delegate?.didUpdateConnectionStatus(isConnected: false)
|
||||
}
|
||||
}
|
||||
|
||||
// CBCentralManagerDelegate - Detectar y conectar al ESP32
|
||||
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
||||
if peripheral.name == "ESP32_BLE_Serial" {
|
||||
self.esp32Peripheral = peripheral
|
||||
self.esp32Peripheral?.delegate = self
|
||||
centralManager.stopScan()
|
||||
centralManager.connect(esp32Peripheral!, options: nil)
|
||||
print("Conectando a \(peripheral.name ?? "ESP32")")
|
||||
}
|
||||
}
|
||||
|
||||
// CBCentralManagerDelegate - Conexión exitosa
|
||||
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||
print("Conectado al ESP32")
|
||||
delegate?.didUpdateConnectionStatus(isConnected: true)
|
||||
esp32Peripheral?.discoverServices(nil) // descubrir todos los servicios
|
||||
}
|
||||
|
||||
// CBCentralManagerDelegate - Desconexión
|
||||
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||
if peripheral == esp32Peripheral {
|
||||
print("Se ha desconectado el ESP32")
|
||||
delegate?.didUpdateConnectionStatus(isConnected: false)
|
||||
}
|
||||
}
|
||||
|
||||
// Descubrir servicios en el ESP32
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
||||
guard let services = peripheral.services else { return }
|
||||
for service in peripheral.services ?? [] {
|
||||
print("Servicio descubierto: \(service.uuid.uuidString)")
|
||||
peripheral.discoverCharacteristics(nil, for: service) // Descubrir todas las características
|
||||
}
|
||||
}
|
||||
|
||||
// Descubrir caracteristicas
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||
guard let characteristics = service.characteristics else { return }
|
||||
for characteristic in characteristics {
|
||||
print("Característica encontrada: \(characteristic.uuid.uuidString)")
|
||||
if characteristic.properties.contains(.notify) {
|
||||
peripheral.setNotifyValue(true, for: characteristic)
|
||||
}
|
||||
self.characteristics[characteristic.uuid] = characteristic // guardar las caracteristicas
|
||||
}
|
||||
}
|
||||
|
||||
// Leer datos recibidos
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
guard let data = characteristic.value, let receivedString = String(data: data, encoding: .utf8) else { return }
|
||||
print("Datos recibidos de \(characteristic.uuid): \(receivedString)")
|
||||
delegate?.didReceiveData(fromCharacteristic: characteristic.uuid.uuidString, data: receivedString)
|
||||
}
|
||||
|
||||
// Enviar un comando al ESP32
|
||||
func sendCommand(_ command: String) {
|
||||
guard let cmdCharacteristic = characteristics[CHARACTERISTIC_UUID_CMD] else { return }
|
||||
let data = Data(("!" + command + "$").utf8)
|
||||
esp32Peripheral?.writeValue(data, for: cmdCharacteristic, type: .withResponse)
|
||||
print("Comando enviado: \(command)")
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
# Documentación BluetoothManager
|
||||
|
||||
## Protocolo BluetoothManagerDelegate
|
||||
Este protocolo funciona como un intermediario entre la comunicación de la clase **BluetoothManager** y otros puntos del proyecto donde sea requerido su uso para poder ser notificados de cambios o eventos existentes.
|
||||
Los métodos definidos en este son los siguientes:
|
||||
* **didUpdateConnectionStatus**: este método tiene como función detectar la conexión o desconexión de un dispositivo Bluetooth.
|
||||
* **didReceiveData**: este método es llamado cuando cuando se recibe información entrante otro dispositivo mediante Bluetooth.
|
||||
|
||||
Este ha de ser implementado en la clase tipo _ViewController_ donde se ha de hacer uso de la conexión Bluetooth, aunque no se limita a una sola ya que permite el uso de **BluetoothManager** en diferentes _ViewControllers_.
|
||||
|
||||
## Clase BluetoothManager
|
||||
Esta clase es una implementación de un gestor de Bluetooth centralizado para la comunicación con dispositivos BLE e implementa los protocolos **CBCentralManagerDelegate** y **CBPeripheralDelegate** encargados de la exploración, conexión (CBCentralManagerDelegate) y transferencia de datos (CBPeripheralDelegate).
|
||||
|
||||
### Propiedades
|
||||
* **shared**: implementación unica de clase en todo el proyecto con el modelo _singleton_ para un acceso facil a los metodos de la misma.
|
||||
* **centralManager**: gestor central de Bluetooth encargado de la detección y conexión de dispositivos BLE.
|
||||
* **esp32Peripheral**: definición de la referencia al periférico del ESP32.
|
||||
* **esp32Characteristic**: definición de la comunicación de lectura y escritura con el ESP32.
|
||||
* **delegate**: definición del delegado opcional conforme al protocolo **BluetoothManagerDelegate** descrito anteriormente para ser notificados de eventos de conexión, desconexión y entrada de datos.
|
||||
|
||||
### Constantes
|
||||
* **SERVICE_UUID**: uuid de identificación del servicio del ESP32.
|
||||
* **CHARACTERISTIC_UUID**: uuid de identificación de la caracteristica del ESP32.
|
||||
|
||||
> [!NOTE]
|
||||
> Ambos UUIDs deben de coincidir con los definidos en el código del ESP32 para que la conexión sea posible y unica con la aplicación.
|
||||
|
||||
### Métodos
|
||||
* **override init ()**: inicializa la instancia unica de la clase y configura a _centralManager_ como el delegado.
|
||||
* **startScanning()**: comienza la busqueda de dispositivos BLE con el _SERVICE_UUID_ definido anteriormente, de no contar con este entonces se buscará la conexión con cualquier dispositivo disponible, cabe decir que el Bluetooth del dispositivo debe de estar encendido.
|
||||
* **disconnect()**: desconecta el oeriférico _esp32Peripheral_ de manera manual.
|
||||
* **centralManagerDidUpdateState(_ central: CBCentralManager)**: maneja los cambios de la conexión Bluetooth mediante el _centralManager_.
|
||||
* **centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber)**: al conectar un dispositivo BLE, este perifica el nombre del periférico, y si coincide con el que busca entonces establece la conexión.
|
||||
* **centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)**: si la conexión ha sido exitosa entonces pone el estado de la conexión del delegate en _true_ y busca el servicio uuid requerido en el esp32.
|
||||
* **centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)**: si la conexión ha sido cancelada, entonces cambia el estado de la conexión del delegate a _false_
|
||||
* **peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?)**: busca los servicios del ESP32, y si este coincide con el requerido, entonces busca características.
|
||||
* **peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)**: si se ha encontrado la caracteristica requerida, entonces habilita las notificaciones de eventos para enviar y recibir datos entre la aplicación y el ESP32.
|
||||
* **peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)**: si hay un evento de entrada de datos a la aplicación, entonces leé la información obtenida y lo convierte a un String con el formato _UTF-8_
|
||||
* **sendCommand(_ command: String)**: envia un comando tipo String al ESP32 mediante la caracteristica, en este caso se ha optado por seguir la siguiente estrucura -> **!comando$**.
|
||||
|
||||
> Peripheral (periférico): es un dispositivo que proporciona datos y servicios a otros dispositivos (centrales). En este caso el periférico es el ESP32 y la central es la aplicación.
|
||||
|
||||
> Characteristic (característica): es un atributo que contiene un valor (datos enviados y recibidos) asi como propiedades asociadas (lectura, escritura o notificaciones), esta se encuentra asociada a un servicio específico del periférico.
|
||||
|
||||
Un ejemplo del funcionamiento del protocolo y de la clase se encuentra en _ActivitiesViewController.swift_.
|
@ -0,0 +1,73 @@
|
||||
//
|
||||
// ConsentDocument.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 10/10/24.
|
||||
//
|
||||
|
||||
import ResearchKit
|
||||
|
||||
public var ConsentDocument: ORKConsentDocument{
|
||||
|
||||
let consentDocument = ORKConsentDocument()
|
||||
consentDocument.title = "Formulario de consentimiento del estudio" //Or other title you prefer
|
||||
|
||||
let sectionTypes: [ORKConsentSectionType] = [
|
||||
.overview,
|
||||
.dataGathering,
|
||||
.privacy,
|
||||
.dataUse,
|
||||
.studySurvey,
|
||||
.studyTasks,
|
||||
.withdrawing
|
||||
]
|
||||
|
||||
let titles = [
|
||||
"Descripción general",
|
||||
"Recopilación de datos",
|
||||
"Privacidad",
|
||||
"Uso de datos",
|
||||
"Encuesta de estudio",
|
||||
"Tareas de estudio",
|
||||
"Retirarse del estudio"
|
||||
]
|
||||
|
||||
var index = 0
|
||||
|
||||
//Section contents
|
||||
let text = [
|
||||
"Sección 1: Descripción general. El presente estudio busca establecer una relación entre el sedentarismo y el deterioro cognitivo, asi como los beneficios que trae el realizar actividad física a padecimientos mentales diversos como la ansiedad o la depresión.",
|
||||
"Sección 2: Recopilación de datos. Los datos a recopilar de momento son el pulso cardiaco, la variabilidad de la frecuencia cardiaca y el tiempo de actividad e inactividad del participante.",
|
||||
"Sección 3: Privacidad. Todos los datos y la información recolectada durante el presente estudio del participante son privados y no serán publicos con la identidad del mismo.",
|
||||
"Sección 4: Uso de datos. Los datos y la información recolectada será usada para entrenar una inteligencia artifical con el fin de establecer una relación entre el sedentarismo y el deterioro cognitivo y el como realizar una actividad física permite controlar o reducir malestares de padecimientos mentales como la depresión y la ansiedad.",
|
||||
"Sección 5: Encuesta de estudio. Para el presente estudio, se requiere llenar un formulario con diversas preguntas relacionadas con el mismo si acepta participar en el mismo.",
|
||||
"Sección 6: Tareas de estudio. Durante el presente estudio se le pedirá que realice actividades diversas como caminar, correr o simplemente estar sentado para registrar información de los sensores de sus dispositivos.",
|
||||
"Sección 7: Retirarse del estudio. El participante podrá retirarse del estudio en cualquier momento sin repercusión alguna."
|
||||
]
|
||||
|
||||
consentDocument.sections = []
|
||||
|
||||
//Add sections
|
||||
for sectionType in sectionTypes {
|
||||
let section = ORKConsentSection(type: sectionType)
|
||||
|
||||
let localizedText = NSLocalizedString(text[sectionTypes.firstIndex(of: sectionType)!], comment: "")
|
||||
let localizedSummary = localizedText.components(separatedBy: ".")[0] + "."
|
||||
|
||||
section.title = NSLocalizedString(titles[index], comment: "")
|
||||
section.summary = localizedSummary
|
||||
section.content = localizedText
|
||||
|
||||
if consentDocument.sections == nil {
|
||||
consentDocument.sections = [section]
|
||||
} else {
|
||||
consentDocument.sections!.append(section)
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
//Signature
|
||||
consentDocument.addSignature(ORKConsentSignature(forPersonWithTitle: "Participant", dateFormatString: nil, identifier: "ConsentDocumentParticipantSignature"))
|
||||
|
||||
return consentDocument
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
# Modelo del control de datos con Firebase-Firestore-Storage
|
||||
## Clase StorageManager
|
||||
Esta clase tiene como objetivo gestionar la carga de información a Firestore generada en los formularios y del documento de consentimiento del estudio en Storage.
|
||||
### Propiedades
|
||||
* **shared**: singleton para una instancia única en todo el proyecto.
|
||||
### Métodos
|
||||
* **uploadPDFConsent(fileURL: URL)**: este carga el documento de consentimiento generado por ResearchKit en Firebase-Storage mediante la siguiente ruta _/userDocuments/User.uid/consent.pdf_, esta se le ha de pasar la URL local del archivo en el dispositivo. Si el archivo ha sido cargado con exito entonces se obtiene la URL del documento en Firebase y se almacena mediante UserManager.
|
||||
* **updateFormToFirestore(type: String, questions: [String], answers: [String])**: este tiene como objetivo subir las respuestas de los formularios MMSE, IPAQ y MR a Firebase-Firestore, recibe el identificador del tipo de formulario **type: String**, un listado con el identificador de las preguntas **questions: [String]** y otro listado con el valor de las respuestas **answers: [String]**. Este método se ha declarado como asincrono **DispatchQueue.global(qos: .background).async** para que no ralentise la aplicación. Crea una variable tipo _DATA_ para que sea posible subir la información. En el caso del formulario MMSE este añade valores adicionales que no fueron agregados anteriormente y que son de vital importancia, como la fecha y el lugar donde se encuentra el usuario al momento de llenar dicho formulario. La ruta de almacenamiento en Firestore es la siguiente _/typeForm/User.uid/DATA_.
|
@ -0,0 +1,85 @@
|
||||
//
|
||||
// StorageManager.swift
|
||||
// App-RK-BM
|
||||
//
|
||||
// Created by Luis Mora on 31/10/24.
|
||||
//
|
||||
|
||||
import FirebaseStorage
|
||||
import FirebaseFirestore
|
||||
|
||||
class StorageManager {
|
||||
static let shared = StorageManager()
|
||||
|
||||
private init() {}
|
||||
|
||||
func uploadPDFConsent(fileURL: URL) {
|
||||
let storageRef = Storage.storage().reference()
|
||||
// ruta en Storage
|
||||
let pdfRef = storageRef.child("userDocuments/\(UserManager.shared.user.uid)/consent.pdf")
|
||||
|
||||
// subir el archivo
|
||||
pdfRef.putFile(from: fileURL, metadata: nil) { metadata, error in
|
||||
if let error = error {
|
||||
print("Error al subir el PDF: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
// obtener la URL de descarga
|
||||
pdfRef.downloadURL { url, error in
|
||||
if let error = error {
|
||||
print("Error al obtener la URL de descarga: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
if let downloadURL = url {
|
||||
print("Archivo subido exitosamente. URL: \(downloadURL.absoluteString)")
|
||||
|
||||
UserManager.shared.user.consentDocumentURL = downloadURL.absoluteString
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFormToFirestore(type: String, questions: [String], answers: [String]) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
var data: [String: String] = [:]
|
||||
|
||||
for (index, question) in questions.enumerated() {
|
||||
if index < answers.count {
|
||||
data[question] = answers[index]
|
||||
}
|
||||
}
|
||||
|
||||
// añadir respueas de MMSE
|
||||
if (type == "MMSE") {
|
||||
let country = LocationUtility.shared.userCountry
|
||||
let state = LocationUtility.shared.userState
|
||||
let locality = LocationUtility.shared.userLocality
|
||||
|
||||
let currentDate = DateUtility.getCurrentDate()
|
||||
// fecha
|
||||
data["currentYear"] = currentDate.year
|
||||
data["currentMonth"] = currentDate.month
|
||||
data["currentDay"] = currentDate.dayNumber
|
||||
data["currentDayWeek"] = currentDate.dayName
|
||||
// localización
|
||||
data["currentCountry"] = !country.isEmpty ? LocationUtility.shared.userCountry : "countryUnavailable"
|
||||
data["currentState"] = !state.isEmpty ? LocationUtility.shared.userState : "stateUnavailable"
|
||||
data["currentLocality"] = !locality.isEmpty ? LocationUtility.shared.userLocality : "localityUnavailable"
|
||||
}
|
||||
|
||||
print(data)
|
||||
|
||||
let db = Firestore.firestore()
|
||||
|
||||
db.collection(type).document(UserManager.shared.user.uid).setData(data) { error in
|
||||
if let error = error {
|
||||
print("Error al subir el formulario: \(error.localizedDescription)")
|
||||
} else {
|
||||
print("Formulario subido exitosamente para el usuario con UID \(UserManager.shared.user.uid).")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
# Modelo de control de usuario USER
|
||||
## Estructura User
|
||||
Esta define un tipo de dato que tiene como objetivo controlar y llevar un registro de las acciones del usuario en la aplicación mediante sus propiedades, por ejemplo, asegurarse que solo pueda llenar una los formularios.
|
||||
### Propiedades
|
||||
* **consentDocumentURL**: almacena la URL del documento de consentimiento del usuario en Firebase para unirse al estudio.
|
||||
* **hasAccepted**: empleada para saber si el usuario ha aceptado unirse al estudio.
|
||||
* **madeMMSE**: empleada para saber si ha llenado el formulario MMSE.
|
||||
* **madeIPAQ**: empleada para saber si ha llenado el formulario IPAQ.
|
||||
* **madeMR**: empleada para saber si ha llenado el formulario Historia Médica.
|
||||
* **uid**: almacena la UID unica del usuario al momento de iniciar sesión en la aplicación.
|
||||
> [!IMPORTANT]
|
||||
> Para hacer un buen uso de esta estructura, esta ha de ser usada unicamente mediante la clase UserManager para tener los datos actualizados.
|
||||
|
||||
## UserManager
|
||||
Este archivo contiene la clase _UserManager_ el cual tiene la función de manejar o permitir la interacción con la estructura User y poder gestionar la aplicación entre los diferentes archivos.
|
||||
|
||||
La clase hace uso del modelo _singleton_ para tener una unica instancia en todo el proyecto
|
||||
|
||||
### Propiedades
|
||||
* user: instancia de la estructura User para hacer uso de ella.
|
||||
* collectionName: nombre de la coleción de Firestore donde se han de guardar los valores de la estructura User para poder ser recuperada.
|
||||
|
||||
### Métodos
|
||||
* **loadUser(uid: String, completion: @escaping (Result<User, Error>) -> Void)**: este tiene como objetivo cargar los datos de la estructura User de Firestore los cuales estan almacenados de la siguiente manera _/collectionName/user.uid/document/_, de no haber valores previos entonces se asignan unos por defecto a cada propiedad de User. Si la operación tiene exito entonces devuelve el objeto **unicamente** para conocer sus valores por la terminal _completion(.success(self.user))_, si falla entonces devuelve un mensaje de error __completion(.failure(notFoundError))_.
|
||||
* **saveUser(completion: @escaping (Bool) -> Void)**: este tiene como objetivo almacenar los valores de la estructura User en un momento dato (por ejemplo al cerrar la aplicación), los valores del documento tienen el mismo nombre que el de la estructura para evitar errores. Si la operación es exitosa entonces devulve True, si falla entonces devuelve False.
|
||||
|
||||
> [!NOTE]
|
||||
> Para almacenar la estructura en Firestore esta debe de ser el tipo _DATA_ el cual es similar a un archivo _JSON_.
|
@ -0,0 +1,36 @@
|
||||
//
|
||||
// User.swift
|
||||
// App-RK-Wearable
|
||||
//
|
||||
// Created by Luis Mora on 11/10/24.
|
||||
//
|
||||
|
||||
struct User: Codable {
|
||||
var consentDocumentURL: String
|
||||
var hasAccepted: Bool
|
||||
var madeMMSE: Bool
|
||||
var madeIPAQ: Bool
|
||||
var madeMR: Bool
|
||||
var uid: String
|
||||
|
||||
// constructor principal
|
||||
init(consentDocumentURL: String, hasAccepted: Bool, madeMMSE: Bool, madeIPAQ: Bool, madeMR: Bool, uid: String) {
|
||||
self.consentDocumentURL = consentDocumentURL
|
||||
self.hasAccepted = hasAccepted
|
||||
self.madeMMSE = madeMMSE
|
||||
self.madeIPAQ = madeIPAQ
|
||||
self.madeMR = madeMR
|
||||
self.uid = uid
|
||||
}
|
||||
|
||||
// constructor vacio con valores iniciales
|
||||
init() {
|
||||
self.consentDocumentURL = ""
|
||||
self.hasAccepted = false
|
||||
self.madeIPAQ = false
|
||||
self.madeMMSE = false
|
||||
self.madeMR = false
|
||||
self.uid = "null"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
//
|
||||
// UserManager.swift
|
||||
// App-RK-BM
|
||||
//
|
||||
// Created by Luis Mora on 31/10/24.
|
||||
//
|
||||
|
||||
import FirebaseFirestore
|
||||
|
||||
class UserManager {
|
||||
static let shared = UserManager() // crea una instancia unica del objeto
|
||||
|
||||
var user = User()
|
||||
let collectionName = "users"
|
||||
|
||||
private init() {} // evitar crear instancias
|
||||
|
||||
func loadUser(uid: String, completion: @escaping (Result<User, Error>) -> Void) {
|
||||
let db = Firestore.firestore()
|
||||
|
||||
db.collection(self.collectionName).document(uid).getDocument { (document, error) in
|
||||
if let error = error {
|
||||
// Caso de error en la conexión o consulta
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
if let document = document, document.exists, let data = document.data() {
|
||||
// cargar datos a user
|
||||
self.user.consentDocumentURL = data ["consentDocumentURL"] as? String ?? ""
|
||||
self.user.hasAccepted = data["hasAccepted"] as? Bool ?? false
|
||||
self.user.madeIPAQ = data["madeIPAQ"] as? Bool ?? false
|
||||
self.user.madeMMSE = data["madeMMSE"] as? Bool ?? false
|
||||
self.user.madeMR = data["madeMR"] as? Bool ?? false
|
||||
self.user.uid = data["uid"] as? String ?? ""
|
||||
// enviar user
|
||||
completion(.success(self.user))
|
||||
} else {
|
||||
// Caso en que el documento no existe
|
||||
let notFoundError = NSError(domain: "", code: 404, userInfo: [NSLocalizedDescriptionKey: "Documento no encontrado"])
|
||||
completion(.failure(notFoundError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveUser(completion: @escaping (Bool) -> Void) {
|
||||
let db = Firestore.firestore()
|
||||
|
||||
let data = [
|
||||
"consentDocumentURL": self.user.consentDocumentURL,
|
||||
"hasAccepted": self.user.hasAccepted,
|
||||
"madeIPAQ": self.user.madeIPAQ,
|
||||
"madeMMSE": self.user.madeMMSE,
|
||||
"madeMR": self.user.madeMR,
|
||||
"uid": self.user.uid
|
||||
] as [String : Any]
|
||||
|
||||
db.collection(self.collectionName).document(self.user.uid).setData(data) { error in
|
||||
if let error = error {
|
||||
print("Error al guardar el user: \(error)")
|
||||
completion(false) // el guardado falló
|
||||
} else {
|
||||
print("User guardado exitosamente")
|
||||
completion(true) // el guardado fue exitoso
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
//
|
||||
// DateUtility.swift
|
||||
// App-RK-BM
|
||||
//
|
||||
// Created by Luis Mora on 31/10/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class DateUtility {
|
||||
static func getCurrentDate() -> (year: String, month: String, dayNumber: String, dayName: String) {
|
||||
let date = Date()
|
||||
let calendar = Calendar.current
|
||||
|
||||
let year1 = calendar.component(.year, from: date).description.capitalized
|
||||
let dayNumber1 = calendar.component(.day, from: date).description.capitalized
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale(identifier: "es_ES")
|
||||
|
||||
dateFormatter.dateFormat = "MMMM"
|
||||
let monthName1 = dateFormatter.string(from: date).capitalized
|
||||
dateFormatter.dateFormat = "EEEE"
|
||||
let dayName1 = dateFormatter.string(from: date).capitalized
|
||||
|
||||
return (year: year1, month: monthName1, dayNumber: dayNumber1, dayName: dayName1)
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
//
|
||||
// LocationUtility.swift
|
||||
// App-RK-BM
|
||||
//
|
||||
// Created by Luis Mora on 31/10/24.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
|
||||
class LocationUtility: NSObject, CLLocationManagerDelegate {
|
||||
static let shared = LocationUtility() // Singleton
|
||||
|
||||
let locationManager = CLLocationManager()
|
||||
private let geocoder = CLGeocoder()
|
||||
|
||||
var userCountry: String = ""
|
||||
var userState: String = ""
|
||||
var userLocality: String = ""
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
locationManager.delegate = self
|
||||
locationManager.desiredAccuracy = kCLLocationAccuracyBest
|
||||
}
|
||||
|
||||
func requestLocationAuthorization() {
|
||||
locationManager.requestWhenInUseAuthorization()
|
||||
}
|
||||
|
||||
func startUpdatingLocation() {
|
||||
locationManager.startUpdatingLocation() // Comienza a recibir actualizaciones de la ubicación
|
||||
}
|
||||
|
||||
func requesLocation() {
|
||||
locationManager.requestLocation()
|
||||
}
|
||||
|
||||
// Método para manejar la ubicación obtenida
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
print("\t Ubicación actualizada: \(locations)")
|
||||
guard let location = locations.last else { return }
|
||||
fetchLocationDetails(location: location) { country, state, locality in
|
||||
DispatchQueue.main.async {
|
||||
if let country = country, let state = state, let locality = locality {
|
||||
self.userCountry = country
|
||||
self.userState = state
|
||||
self.userLocality = locality
|
||||
print("Ubicación almacenada: \(country), \(state), \(locality)")
|
||||
NotificationCenter.default.post(name: .locationReady, object: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.locationManager.stopUpdatingLocation() // Detenemos la actualización si solo necesitamos una vez
|
||||
}
|
||||
|
||||
// Función para obtener detalles de ubicación usando CLGeocoder
|
||||
private func fetchLocationDetails(location: CLLocation, completion: @escaping (String?, String?, String?) -> Void) {
|
||||
geocoder.reverseGeocodeLocation(location) { placemarks, error in
|
||||
if let error = error {
|
||||
print("Error al obtener los detalles de la ubicación: \(error.localizedDescription)")
|
||||
completion(nil, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let placemark = placemarks?.first {
|
||||
let country = placemark.country
|
||||
let state = placemark.administrativeArea
|
||||
let locality = placemark.locality
|
||||
completion(country, state, locality)
|
||||
} else {
|
||||
completion(nil, nil, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manejo de errores en caso de falla
|
||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
print("Error al obtener la ubicación: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
// Opcional: Método delegado para manejar cambios en el estado de autorización
|
||||
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
||||
if manager.authorizationStatus == .authorizedWhenInUse || manager.authorizationStatus == .authorizedAlways {
|
||||
self.startUpdatingLocation()
|
||||
} else {
|
||||
print("Permiso de ubicación denegado o restringido")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
static let locationUpdated = Notification.Name("locationUpdated")
|
||||
|
||||
static let locationReady = Notification.Name("locationReady")
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
# Documentación de utilidades
|
||||
## Clase Date Utility
|
||||
Esta tiene como fin contener todas las funciones referentes en cuanto al manejo de fechas en la aplicación.
|
||||
|
||||
### Métodos
|
||||
* **getCurrentDate()**: este tiene como fin devolver 3 valores tipo String, el año, mes, número y nombre del dia actual, en español similar al siguiente ejemplo: _**Jueves-14-Noviembre-2024**_.
|
||||
|
||||
## Clase Location Utility
|
||||
Esta clase tiene como fin gestionar la ubicación GPS actual del usuario en la aplicación. Hace uso del framework **CoreLocation** para poder trabajar con servicios de ubicación, se ha de heredar el protocolo_CLLocationManagerDelegate_ en la clase para manejar eventos relacionados con la ubicación.
|
||||
|
||||
### Propiedades
|
||||
* **shared**: definición singleton para una unica intancia de la clase en todo el proyecto.
|
||||
* **locationManager**: gestiona el servicio de ubicación.
|
||||
* **geocoder**: empleada para la decodificación inversa de la ubicación el usuario.
|
||||
|
||||
### Variables
|
||||
* **userCountry**: almacena el país del usuario.
|
||||
* **userState**: almacena el estado del usuario.
|
||||
* **userLocality**: almacena la localidad del usuario, como pueblo, o cuidad.
|
||||
|
||||
### Métodos
|
||||
* **private override init()**: inicializador privado que configura el delegado y la precisión de la ubicación
|
||||
* **requestLocationAuthorization()**: solicita el acceso a la ubicación cuando la aplicación esta siendo usada.
|
||||
* **startUpdatingLocation()**: comienza a obtener la ubicación del usuario.
|
||||
* **requesLocation()**: solicita una unica actualización de la ubicación actual del usuario.
|
||||
* **locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])**: método delegado que es llamado cuando la ubicación del usuario es actualizada, llama a _fetchLocationDetails_ para obtener detalles de la ubicación del usuario y una vez hecho esto detiene la actualización de la ubicación.
|
||||
* **fetchLocationDetails(location: CLLocation, completion: @escaping (String?, String?, String?) -> Void)**: realiza la geocodificación inversa del usuario para obtener detalles de su ubicación (país, estado y localidad) en base a sus coordenadas. Devuelve los datos en un _completion_
|
||||
* **locationManager(_ manager: CLLocationManager, didFailWithError error: Error)**: contiene el código que se ha de ejecutar en caso de no poder obtener la ubicación del usuario por algun error.
|
||||
* **locationManagerDidChangeAuthorization(_ manager: CLLocationManager)**: método que verifica si la aplicación tiene permisos de ubicación, si es asi entonces comienza a obtener la ubicación mediante el método _startUpdatingLocation()_
|
||||
|
||||
### Extensión Notification.Name
|
||||
Aqui se definen notificaciones personalizadas que se han de usar para dar aviso a otras partes de la aplicación sobre cuando la ubicación ha sido actualizada o si esta disponible.
|
||||
|
||||
### Uso
|
||||
1. Solicitar permisos para usar la ubicación: _ LocationUtility.shared.requestLocationAuthorization()_.
|
||||
2. si se tienen permisos, se comienza a ibtener la ubicación de manera continua _startUpdatingLocation()_ o una unica vez _requesLocation()_.
|
||||
3. Una vez obtenida la ubicación, se llama el método _fetchLocationDetails_ para obtener detalles de la ubicación.
|
||||
4. Una vez obtenidos los detalles de la ubicación, estos se almacenan en las variables de LocationUtility para ser usadas en otras partes del código.
|
Binary file not shown.
@ -0,0 +1,411 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Initial View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="IntroViewController" title="Initial View Controller" id="BYZ-38-t0r" customClass="IntroViewController" customModule="App_iOS_Wearable" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
<connections>
|
||||
<segue destination="vmF-GY-36f" kind="custom" identifier="toConsent" customClass="IntroSegue" customModule="App_iOS_Wearable" customModuleProvider="target" id="Me7-ps-tMf"/>
|
||||
<segue destination="OLQ-sH-saM" kind="custom" identifier="toTasks" customClass="IntroSegue" customModule="App_iOS_Wearable" customModuleProvider="target" id="qEd-wn-ZxX"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="20" y="-34"/>
|
||||
</scene>
|
||||
<!--Consent View Controller-->
|
||||
<scene sceneID="hKe-Av-Nm3">
|
||||
<objects>
|
||||
<viewController title="Consent View Controller" id="vmF-GY-36f" customClass="ConsentViewController" customModule="App_iOS_Wearable" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Gyu-5C-c1N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="842"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Tnc-29-hO3">
|
||||
<rect key="frame" x="121" y="610.66666666666663" width="151" height="34.333333333333371"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="tinted" title="Unirse al estudio"/>
|
||||
<connections>
|
||||
<action selector="joinButtonTapped:" destination="vmF-GY-36f" eventType="touchUpInside" id="UL8-Pl-ihZ"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="uNB-F3-JBW">
|
||||
<rect key="frame" x="121" y="255" width="150" height="84"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Autenticación" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="F0I-Pn-MZu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="150" height="38"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3hw-kF-cCb">
|
||||
<rect key="frame" x="0.0" y="46" width="150" height="38"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="gray" image="googleIcon" title="Google"/>
|
||||
<connections>
|
||||
<action selector="googleButtonTapped:" destination="vmF-GY-36f" eventType="touchUpInside" id="cyA-TR-E7t"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="150" id="ACu-N2-mqa"/>
|
||||
<constraint firstAttribute="width" constant="150" id="xgA-1l-8Yp"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="2S2-96-Ned"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="2S2-96-Ned" firstAttribute="trailing" secondItem="uNB-F3-JBW" secondAttribute="trailing" constant="122" id="DUl-s6-hmC"/>
|
||||
<constraint firstItem="2S2-96-Ned" firstAttribute="bottom" secondItem="Tnc-29-hO3" secondAttribute="bottom" constant="197" id="FW4-Hv-m9h"/>
|
||||
<constraint firstItem="2S2-96-Ned" firstAttribute="trailing" secondItem="Tnc-29-hO3" secondAttribute="trailing" constant="121" id="eZo-4x-fkZ"/>
|
||||
<constraint firstItem="uNB-F3-JBW" firstAttribute="top" secondItem="2S2-96-Ned" secondAttribute="top" constant="255" id="oMP-xd-pBq"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="qWp-Lv-Ulz"/>
|
||||
<connections>
|
||||
<outlet property="googleButton" destination="3hw-kF-cCb" id="wI0-E6-Gkh"/>
|
||||
<outlet property="joinButton" destination="Gyu-5C-c1N" id="rBr-MM-Vlx"/>
|
||||
<segue destination="ddS-Av-vTr" kind="unwind" identifier="unwindToTasks" unwindAction="unwindToTasks:" id="cwJ-Vu-XzX"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="XEh-Ng-Lec" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
<exit id="ddS-Av-vTr" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="993" y="-374"/>
|
||||
</scene>
|
||||
<!--Forms View Controller-->
|
||||
<scene sceneID="6ij-N0-oti">
|
||||
<objects>
|
||||
<viewController title="Forms View Controller" id="KX0-qu-BAE" customClass="FormsViewController" customModule="App_iOS_Wearable" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Fp4-dF-zzz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LYv-9y-gSa">
|
||||
<rect key="frame" x="0.0" y="50" width="108" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="chevron.backward" catalog="system" title="Regresar"/>
|
||||
<connections>
|
||||
<action selector="backButtonTapped:" destination="KX0-qu-BAE" eventType="touchUpInside" id="Zeu-jF-Ytk"/>
|
||||
<segue destination="6a0-q8-4dY" kind="unwind" identifier="unwindToTaskView" unwindAction="unwindToTaskView:" id="MHX-AW-iPI"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="ZIx-bi-iDt">
|
||||
<rect key="frame" x="60" y="184.33333333333331" width="273" height="161.33333333333331"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Formularios" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jKJ-km-kxt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="273" height="34.333333333333336"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yYS-JV-Sup">
|
||||
<rect key="frame" x="0.0" y="42.333333333333314" width="273" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="brain" catalog="system" title="MMSE"/>
|
||||
<connections>
|
||||
<action selector="mmseButtonTapped:" destination="KX0-qu-BAE" eventType="touchUpInside" id="rj4-HB-qfl"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QXy-ge-uVp">
|
||||
<rect key="frame" x="0.0" y="84.666666666666657" width="273" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="accessibility" catalog="system" title="IPAQ"/>
|
||||
<connections>
|
||||
<action selector="ipaqButtonTapped:" destination="KX0-qu-BAE" eventType="touchUpInside" id="kqN-n2-VUT"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uGX-xt-MVm">
|
||||
<rect key="frame" x="0.0" y="126.99999999999996" width="273" height="34.333333333333329"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="stethoscope" catalog="system" title="Historia Clinica"/>
|
||||
<connections>
|
||||
<action selector="recordButtonTapped:" destination="KX0-qu-BAE" eventType="touchUpInside" id="mgm-U2-3qk"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="tYd-q1-D8n"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="LYv-9y-gSa" firstAttribute="leading" secondItem="tYd-q1-D8n" secondAttribute="leading" id="Jd6-f3-rtG"/>
|
||||
<constraint firstItem="LYv-9y-gSa" firstAttribute="top" secondItem="tYd-q1-D8n" secondAttribute="top" constant="50" id="OTp-fc-rPg"/>
|
||||
<constraint firstItem="ZIx-bi-iDt" firstAttribute="top" secondItem="LYv-9y-gSa" secondAttribute="bottom" constant="100" id="bZZ-6W-deW"/>
|
||||
<constraint firstItem="tYd-q1-D8n" firstAttribute="trailing" secondItem="ZIx-bi-iDt" secondAttribute="trailing" constant="60" id="ekE-x9-AoX"/>
|
||||
<constraint firstItem="ZIx-bi-iDt" firstAttribute="leading" secondItem="tYd-q1-D8n" secondAttribute="leading" constant="60" id="w3l-ON-Q1j"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="5Ip-ZS-SOy"/>
|
||||
<connections>
|
||||
<outlet property="ipaqButton" destination="QXy-ge-uVp" id="7jx-g3-K13"/>
|
||||
<outlet property="mmseButton" destination="yYS-JV-Sup" id="yI2-ue-2LB"/>
|
||||
<outlet property="recordButton" destination="uGX-xt-MVm" id="NHC-cR-wIV"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="GuF-2W-M9c" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
<exit id="6a0-q8-4dY" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2250" y="-61"/>
|
||||
</scene>
|
||||
<!--Activities View Controller-->
|
||||
<scene sceneID="M9l-7T-uv5">
|
||||
<objects>
|
||||
<viewController title="Activities View Controller" id="I2a-g2-dlI" customClass="ActivitiesViewController" customModule="App_iOS_Wearable" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="aY0-Di-EMI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yhH-EN-unQ">
|
||||
<rect key="frame" x="0.0" y="50" width="108" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="chevron.backward" catalog="system" title="Regresar"/>
|
||||
<connections>
|
||||
<segue destination="FPa-yg-Rkz" kind="unwind" identifier="unwindToTaskView" unwindAction="unwindToTaskView:" id="eWR-EP-z9I"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="s05-0e-HqC">
|
||||
<rect key="frame" x="71" y="180.00000000000003" width="250" height="330.66666666666674"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Estado: " textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UkK-fG-p10">
|
||||
<rect key="frame" x="0.0" y="0.0" width="250" height="34.333333333333336"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kyQ-rW-Uy8">
|
||||
<rect key="frame" x="0.0" y="42.333333333333343" width="250" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="filled" title="Conectar"/>
|
||||
<connections>
|
||||
<action selector="connectButtonTapped:" destination="I2a-g2-dlI" eventType="touchUpInside" id="tCN-Es-f8e"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Vvw-Dd-DM2">
|
||||
<rect key="frame" x="0.0" y="84.666666666666686" width="250" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="filled" title="Desconectar">
|
||||
<color key="baseBackgroundColor" red="0.91976243260000001" green="0.085834366149999994" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
</buttonConfiguration>
|
||||
<connections>
|
||||
<action selector="disconnectButtonTapped:" destination="I2a-g2-dlI" eventType="touchUpInside" id="0DR-4R-htx"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Eyk-b0-Ele">
|
||||
<rect key="frame" x="0.0" y="126.99999999999999" width="250" height="34.333333333333329"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="gray" title="Iniciar Actividad"/>
|
||||
<connections>
|
||||
<action selector="startActivityButtonTapped:" destination="I2a-g2-dlI" eventType="touchUpInside" id="FDo-eG-IO9"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7Bf-X7-cjT">
|
||||
<rect key="frame" x="0.0" y="169.33333333333331" width="250" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="gray" title="Detener Actividad"/>
|
||||
<connections>
|
||||
<action selector="endActivityButtonTapped:" destination="I2a-g2-dlI" eventType="touchUpInside" id="pWk-x7-Zk8"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RdX-1J-PUf">
|
||||
<rect key="frame" x="0.0" y="211.66666666666669" width="250" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="gray" title="Cancelar actividad"/>
|
||||
<connections>
|
||||
<action selector="cancelActivityButtonTapped:" destination="I2a-g2-dlI" eventType="touchUpInside" id="59U-Za-QLY"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Fan-Ca-9LD">
|
||||
<rect key="frame" x="0.0" y="254.00000000000003" width="250" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="gray" title="Obtener datos"/>
|
||||
<connections>
|
||||
<action selector="getDataButtonTapped:" destination="I2a-g2-dlI" eventType="touchUpInside" id="1tv-N4-nWt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Datos recibidos: " textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="C6U-yd-wQR">
|
||||
<rect key="frame" x="0.0" y="296.33333333333331" width="250" height="34.333333333333314"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="250" id="Fdt-OT-hvo"/>
|
||||
<constraint firstItem="kyQ-rW-Uy8" firstAttribute="top" secondItem="UkK-fG-p10" secondAttribute="bottom" constant="100" id="OSh-y0-uWR"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Texto de prueba" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Gsc-1e-ZIl">
|
||||
<rect key="frame" x="134" y="559" width="124" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="vup-ms-2SU"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="yhH-EN-unQ" firstAttribute="leading" secondItem="vup-ms-2SU" secondAttribute="leading" id="LYp-aV-fPS"/>
|
||||
<constraint firstItem="s05-0e-HqC" firstAttribute="top" secondItem="vup-ms-2SU" secondAttribute="top" constant="180" id="MnY-jh-kRv"/>
|
||||
<constraint firstItem="vup-ms-2SU" firstAttribute="trailing" secondItem="s05-0e-HqC" secondAttribute="trailing" constant="72" id="qTr-9l-B8y"/>
|
||||
<constraint firstItem="yhH-EN-unQ" firstAttribute="top" secondItem="vup-ms-2SU" secondAttribute="top" constant="50" id="sVd-Lc-V6r"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Actividades" largeTitleDisplayMode="always" id="ql3-ak-mB9">
|
||||
<barButtonItem key="backBarButtonItem" title="Back" id="730-el-xad"/>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="connectButton" destination="kyQ-rW-Uy8" id="XVJ-fU-s3H"/>
|
||||
<outlet property="dataLabel" destination="C6U-yd-wQR" id="kLF-Ko-qmJ"/>
|
||||
<outlet property="disconnectButton" destination="Vvw-Dd-DM2" id="cho-pl-Dws"/>
|
||||
<outlet property="endActivityButton" destination="7Bf-X7-cjT" id="gDq-4G-KHL"/>
|
||||
<outlet property="getDataButton" destination="Fan-Ca-9LD" id="3eD-eg-NmK"/>
|
||||
<outlet property="startActivityButton" destination="Eyk-b0-Ele" id="FXo-g3-e7Z"/>
|
||||
<outlet property="stateLabel" destination="UkK-fG-p10" id="taf-tA-McC"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="glU-IZ-qly" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
<exit id="FPa-yg-Rkz" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2251" y="658"/>
|
||||
</scene>
|
||||
<!--Task View Controller-->
|
||||
<scene sceneID="RLt-pH-54a">
|
||||
<objects>
|
||||
<viewController title="Task View Controller" id="OLQ-sH-saM" customClass="TasksViewController" customModule="App_iOS_Wearable" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="z9A-Lt-pzn">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eEW-Od-AfA">
|
||||
<rect key="frame" x="8" y="659.33333333333337" width="350" height="54.333333333333371"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vHA-mw-xdA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="175" height="54.333333333333336"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="person.2.fill" catalog="system" title="Invitar al estudio"/>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gzB-vT-nqb">
|
||||
<rect key="frame" x="175" y="0.0" width="175" height="54.333333333333336"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="document.circle.fill" catalog="system" title="Documento de consentimiento"/>
|
||||
<connections>
|
||||
<action selector="documentButtonTapped:" destination="OLQ-sH-saM" eventType="touchUpInside" id="0nP-IE-fxl"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="350" id="Xlh-BQ-39M"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0qv-ga-9nF">
|
||||
<rect key="frame" x="123.66666666666667" y="733.66666666666663" width="145.33333333333331" height="34.333333333333371"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" title="Salir del estudio">
|
||||
<color key="baseForegroundColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</buttonConfiguration>
|
||||
<connections>
|
||||
<action selector="leaveButtonTapped:" destination="OLQ-sH-saM" eventType="touchUpInside" id="x9O-i7-1Xz"/>
|
||||
<segue destination="vmF-GY-36f" kind="show" identifier="returnToConsent" id="yTp-kn-hsu"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="LAm-DR-bGm">
|
||||
<rect key="frame" x="96" y="425.66666666666669" width="200" height="78.666666666666686"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RkJ-kK-eM4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="34.333333333333336"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="filled" title="Formularios"/>
|
||||
<connections>
|
||||
<action selector="formsButtonTapped:" destination="OLQ-sH-saM" eventType="touchUpInside" id="oBr-Z2-SwH"/>
|
||||
<segue destination="KX0-qu-BAE" kind="presentation" identifier="toForms" modalPresentationStyle="fullScreen" id="bh5-Nx-VQ6"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Qjb-CI-QvM">
|
||||
<rect key="frame" x="0.0" y="44.333333333333314" width="200" height="34.333333333333343"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="filled" title="Actividades"/>
|
||||
<connections>
|
||||
<action selector="activitiesButtonTapped:" destination="OLQ-sH-saM" eventType="touchUpInside" id="Fmr-34-LSL"/>
|
||||
<segue destination="I2a-g2-dlI" kind="presentation" identifier="toActivities" modalPresentationStyle="fullScreen" id="eJs-OR-igC"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="200" id="zlo-b5-dxl"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="oUr-Ga-eJe">
|
||||
<rect key="frame" x="72" y="134" width="250" height="20.333333333333343"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="L6F-U7-WH9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="250" height="20.333333333333332"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="250" id="0R4-hP-cUk"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="GaP-oB-Yjf"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="GaP-oB-Yjf" firstAttribute="trailing" secondItem="oUr-Ga-eJe" secondAttribute="trailing" constant="71" id="38M-Gp-ePR"/>
|
||||
<constraint firstItem="GaP-oB-Yjf" firstAttribute="trailing" secondItem="LAm-DR-bGm" secondAttribute="trailing" constant="97" id="9Cu-Ce-zPb"/>
|
||||
<constraint firstItem="0qv-ga-9nF" firstAttribute="top" secondItem="eEW-Od-AfA" secondAttribute="bottom" constant="20" id="ARH-aF-o60"/>
|
||||
<constraint firstItem="GaP-oB-Yjf" firstAttribute="trailing" secondItem="0qv-ga-9nF" secondAttribute="trailing" constant="124" id="Bvd-eV-HWO"/>
|
||||
<constraint firstItem="GaP-oB-Yjf" firstAttribute="bottom" secondItem="0qv-ga-9nF" secondAttribute="bottom" constant="50" id="I69-jy-l69"/>
|
||||
<constraint firstItem="GaP-oB-Yjf" firstAttribute="trailing" secondItem="eEW-Od-AfA" secondAttribute="trailing" constant="35" id="aSd-6J-aMf"/>
|
||||
<constraint firstItem="oUr-Ga-eJe" firstAttribute="top" secondItem="GaP-oB-Yjf" secondAttribute="top" constant="75" id="lEg-Vv-jfx"/>
|
||||
<constraint firstItem="eEW-Od-AfA" firstAttribute="top" secondItem="LAm-DR-bGm" secondAttribute="bottom" constant="155" id="ms4-Vl-ZWZ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="activitiesButton" destination="Qjb-CI-QvM" id="Olh-Ng-fIS"/>
|
||||
<outlet property="documentButton" destination="gzB-vT-nqb" id="ZaV-rh-tzd"/>
|
||||
<outlet property="formsButton" destination="RkJ-kK-eM4" id="l4n-vD-YHP"/>
|
||||
<outlet property="leaveButton" destination="0qv-ga-9nF" id="wiF-c1-m0s"/>
|
||||
<outlet property="userLabel" destination="L6F-U7-WH9" id="jxL-Oy-hv7"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="7Lq-vc-IqC" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="991.60305343511448" y="315.49295774647891"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="yTp-kn-hsu"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<resources>
|
||||
<image name="accessibility" catalog="system" width="128" height="123"/>
|
||||
<image name="brain" catalog="system" width="128" height="107"/>
|
||||
<image name="chevron.backward" catalog="system" width="97" height="128"/>
|
||||
<image name="document.circle.fill" catalog="system" width="128" height="123"/>
|
||||
<image name="googleIcon" width="24" height="24"/>
|
||||
<image name="person.2.fill" catalog="system" width="128" height="86"/>
|
||||
<image name="stethoscope" catalog="system" width="128" height="101"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
Loading…
Reference in New Issue