Why can't I subclass 'OCKSurveyTaskViewController' from CareKit? - swift

Note: I tried posting on the apple developer forums 2 days ago and didn't receive any responses as well
I've been literally stuck on this portion of my project because I want to mirror the way apple has their care view setup for displaying tasks to users. I for some reason, can't subclass the 'OCKSurveyTaskViewController' as the error I'm getting is:
"Cannot find type 'OCKSurveyTaskViewController' in scope"
I've reinstalled CareKit through SPM 2 or three times and I can't figure out why I can't subclass it even when I see the exact OCKSurveyTaskViewController.swift file and it listed in the code as an open class in the CareKit/iOS/Task/ViewController directory from Xcode.
Could anyone please give me some guidance or perhaps another way to display the ORKTasks I have established for users on a daily basis in another fashion? I prefer the method apple Is using in their wwdc21 CareKit code along but alas I've been stuck on this for too long and this is needed to get to my next step.
Here's my files code:
import UIKit
import CareKit
import CareKitUI
import CareKitStore
import ResearchKit
import os.log
class StudyTaskFeedViewController: OCKDailyTasksPageViewController, OCKSurveyTaskViewController {}
Here's the code to the OCKSurveyTaskViewController.swift file:
#if !os(watchOS) && canImport(ResearchKit)
import CareKitStore
import CareKitUI
import ResearchKit
import UIKit
// MARK: OCKSurveyTaskViewControllerDelegate
public protocol OCKSurveyTaskViewControllerDelegate: AnyObject {
func surveyTask(
viewController: OCKSurveyTaskViewController,
for task: OCKAnyTask,
didFinish result: Result<ORKTaskViewControllerFinishReason, Error>)
func surveyTask(
viewController: OCKSurveyTaskViewController,
shouldAllowDeletingOutcomeForEvent event: OCKAnyEvent) -> Bool
}
public extension OCKSurveyTaskViewControllerDelegate {
func surveyTask(
viewController: OCKSurveyTaskViewController,
for task: OCKAnyTask,
didFinish result: Result<ORKTaskViewControllerFinishReason, Error>) {
// No-op
}
func surveyTask(
viewController: OCKSurveyTaskViewController,
shouldAllowDeletingOutcomeForEvent event: OCKAnyEvent) -> Bool {
return true
}
}
open class OCKSurveyTaskViewController: OCKTaskViewController<OCKTaskController, OCKSurveyTaskViewSynchronizer>, ORKTaskViewControllerDelegate {
private let extractOutcome: (ORKTaskResult) -> [OCKOutcomeValue]?
public let survey: ORKTask
public weak var surveyDelegate: OCKSurveyTaskViewControllerDelegate?
public convenience init(
task: OCKAnyTask,
eventQuery: OCKEventQuery,
storeManager: OCKSynchronizedStoreManager,
survey: ORKTask,
viewSynchronizer: OCKSurveyTaskViewSynchronizer = OCKSurveyTaskViewSynchronizer(),
extractOutcome: #escaping (ORKTaskResult) -> [OCKOutcomeValue]?) {
self.init(
taskID: task.id,
eventQuery: eventQuery,
storeManager: storeManager,
survey: survey,
viewSynchronizer: viewSynchronizer,
extractOutcome: extractOutcome
)
}
public init(
taskID: String,
eventQuery: OCKEventQuery,
storeManager: OCKSynchronizedStoreManager,
survey: ORKTask,
viewSynchronizer: OCKSurveyTaskViewSynchronizer = OCKSurveyTaskViewSynchronizer(),
extractOutcome: #escaping (ORKTaskResult) -> [OCKOutcomeValue]?) {
self.survey = survey
self.extractOutcome = extractOutcome
super.init(
viewSynchronizer: viewSynchronizer,
taskID: taskID,
eventQuery: eventQuery,
storeManager: storeManager
)
}
override open func taskView(
_ taskView: UIView & OCKTaskDisplayable,
didCompleteEvent isComplete: Bool,
at indexPath: IndexPath,
sender: Any?) {
guard isComplete else {
if let event = controller.eventFor(indexPath: indexPath),
let delegate = surveyDelegate,
delegate.surveyTask(
viewController: self,
shouldAllowDeletingOutcomeForEvent: event) == false {
return
}
let cancelAction = UIAlertAction(
title: "Cancel",
style: .cancel,
handler: nil
)
let confirmAction = UIAlertAction(
title: "Delete", style: .destructive) { _ in
super.taskView(
taskView,
didCompleteEvent: isComplete,
at: indexPath,
sender: sender
)
}
let warningAlert = UIAlertController(
title: "Delete",
message: "Are you sure you want to delete your response?",
preferredStyle: .actionSheet
)
warningAlert.addAction(cancelAction)
warningAlert.addAction(confirmAction)
present(warningAlert, animated: true, completion: nil)
return
}
let surveyViewController = ORKTaskViewController(
task: survey,
taskRun: nil
)
let directory = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
).last!.appendingPathComponent("ResearchKit", isDirectory: true)
surveyViewController.outputDirectory = directory
surveyViewController.delegate = self
present(surveyViewController, animated: true, completion: nil)
}
// MARK: ORKTaskViewControllerDelegate
open func taskViewController(
_ taskViewController: ORKTaskViewController,
didFinishWith reason: ORKTaskViewControllerFinishReason,
error: Error?) {
taskViewController.dismiss(animated: true, completion: nil)
guard let task = controller.taskEvents.first?.first?.task else {
assertionFailure("Task controller is missing its task")
return
}
if let error = error {
surveyDelegate?.surveyTask(
viewController: self,
for: task,
didFinish: .failure(error)
)
return
}
guard reason == .completed else {
return
}
let indexPath = IndexPath(item: 0, section: 0)
guard let event = controller.eventFor(indexPath: indexPath) else {
return
}
guard let values = extractOutcome(taskViewController.result) else {
return
}
let outcome = OCKOutcome(
taskUUID: event.task.uuid,
taskOccurrenceIndex: event.scheduleEvent.occurrence,
values: values
)
controller.storeManager.store.addAnyOutcome(
outcome,
callbackQueue: .main) { result in
if case let .failure(error) = result {
self.surveyDelegate?.surveyTask(
viewController: self,
for: task,
didFinish: .failure(error)
)
}
self.surveyDelegate?.surveyTask(
viewController: self,
for: task,
didFinish: .success(reason)
)
}
}
}
#endif

I encountered the same problem when I try to reproduce the demostration code form the "Recover" app in my own app. After I compared my project settings with the "Recover", I noticed that I missed to add the capability of "HealthKit" in my target app's Sign&Capabilities. After I added this capability, it worked.

Related

Add & Cancel buttons in EventKit not working

Im using event kit to create a reminder, but when I press add or cancel the window does not close. if I go into the calendars app I can see the item in there. I've tried adding "editviewDelegate = self" but I always get an error saying "Cannot assign value of type 'ViewController?' to type 'EKEventEditViewDelegate"
import UIKit
import EventKit
import EventKitUI
class ViewController: UIViewController, EKEventViewDelegate {
func eventViewController(_ controller: EKEventViewController, didCompleteWith action: EKEventViewAction) {
controller.dismiss(animated: true, completion: nil)
}
let store = EKEventStore()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addnew))
}
#objc func addnew() {
store.requestAccess(to: .event) { [weak self] success, error in
if success, error == nil {
DispatchQueue.main.async {
guard let store = self?.store else {return}
let othervc = EKEventEditViewController()
othervc.eventStore = store
othervc.event = EKEvent(eventStore: store)
self?.present(othervc, animated: true, completion: nil)
}
}
}
}

Error trying to send an email using messageui library

Using the code below found on another stack overflow page I keep getting this error when a button is pressed to form an email within my app I am currently working on. Can someone help?
error: [PPT] Error creating the CFMessagePort needed to communicate with PPT.
import Foundation
import MessageUI
class EmailHelper: NSObject, MFMailComposeViewControllerDelegate {
public static let shared = EmailHelper()
private override init() {
//
}
func sendEmail(subject:String, body:String, to:String){
if !MFMailComposeViewController.canSendMail() {
// Utilities.showErrorBanner(title: "No mail account found", subtitle: "Please setup a mail account")
return //EXIT
}
let picker = MFMailComposeViewController()
picker.setSubject(subject)
picker.setMessageBody(body, isHTML: true)
picker.setToRecipients([to])
picker.mailComposeDelegate = self
EmailHelper.getRootViewController()?.present(picker, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
EmailHelper.getRootViewController()?.dismiss(animated: true, completion: nil)
}
static func getRootViewController() -> UIViewController? {
(UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController
// OR If you use SwiftUI 2.0 based WindowGroup try this one
// UIApplication.shared.windows.first?.rootViewController
}
}
Button(action: {
EmailHelper.shared.sendEmail(subject: "Anything...", body: "", to: "")
}) {
Text("Send Email")
}

Delegates and protocols with VC not connected

I have a parent view controller i.e. HomeViewController which has a navigation bar button via which a user can trigger an alert and enter a string. This string needs to be passed to the child view controller.
Here is the relevant code in the parent view controller:
protocol NewSectionDelegate {
func sendSectionName(name : String)
}
class HomeViewController: UIViewController {
var sectionNameDelegate : NewSectionDelegate?
func addCardAsChild() { // add the child VC to the parent VC
if cardViewController == nil {
cardViewController = CardViewController()
addViewController(newViewController: cardViewController!)
} else {
addViewController(newViewController: cardViewController!)
}
}
func triggerAlert() {
let alertController = UIAlertController(title: "New section", message: "Name the section with a words or a sentence", preferredStyle: .alert)
alertController.addTextField(configurationHandler:
{(_ textField: UITextField) -> Void in //txtview customization
})
let addAction = UIAlertAction(title: "Add", style: .default) { _ in
guard let sectionName = alertController.textFields?.first?.text else { return }
self.sectionNameDelegate?.sendSectionName(name: sectionName) // Sending string; verified that the string is not nil
}
alertController.addAction(addAction)
self.present(alertController, animated: true, completion: nil)
}
And here is the child view controller:
class CardViewController: UIViewController, NewSectionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let homeViewController = HomeViewController()
homeViewController.sectionNameDelegate = self
}
func sendSectionName(name: String) {
print("received name:\(name)") // This line of code is never called
}
The data is not getting passed and I have no idea why.
Is this what you are looking for?
protocol NewSectionDelegate {
func sendSectionName(name : String)
}
class HomeViewController: UIViewController {
var sectionNameDelegate : NewSectionDelegate?
var cardViewController = CardViewController()
func addCardAsChild() { // add the child VC to the parent VC
self.addChild(self.cardViewController)
}
func triggerAlert() {
let alertController = UIAlertController(title: "New section", message: "Name the section with a words or a sentence", preferredStyle: .alert)
alertController.addTextField(configurationHandler:
{(_ textField: UITextField) -> Void in //txtview customization
})
let addAction = UIAlertAction(title: "Add", style: .default) { _ in
guard let sectionName = alertController.textFields?.first?.text else { return }
self.sectionNameDelegate?.sendSectionName(name: sectionName) // Sending string; verified that the string is not nil
}
alertController.addAction(addAction)
self.present(alertController, animated: true, completion: nil)
}
}
class CardViewController: UIViewController, NewSectionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
guard let homeViewController = self.parent as? HomeViewController else { return }
homeViewController.sectionNameDelegate = self
}
func sendSectionName(name: String) {
print("received name:\(name)") // This line of code is never called
}
}
I think the delegate method is getting called but for the internal HomeViewController you're making in the ChildViewController viewDidLoad method. It looks like you're expecting the method to get called on a different object. I would remove that code from viewDidLoad and set the sectionNameDelegate in HomeViewController

How to send selected cell to another view controller?

Hey guys so I'm working on an application where I have imported a contacts list from my device and I am given the option to "add" the contact but it really doesn't do much when it comes to functionality. I'm not the best coder so try to hear me out. what I am trying to do is take the data/ selected table view cell and display it on another page. I "think" that this is what I should do because I have tried to display the data on another page but get an error when I move my OVERRIDE function. that makes me believe that I need to take the data, which I believe is newContact? and set that as a variable and then display it on a new page where I can create a new view controller and add the code without error.
I essentially need to figure out what my JSON data is saved as, then set that equivalent to a string if that is possible, so I can send it to my new view controller or and send it to my database with code I already have created.
I am just not sure where to enter the statements because of errors that I am getting and what the exact code would be.
Sorry for the awful description of what I am trying to perform, I have a grasp of what is needed to be done but I am a beginner.
My Master View Controller that takes the contacts from my phone and accesses them.
import UIKit
import Contacts
import ContactsUI
class MainViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var tableView: UITableView!
var store = CNContactStore()
var contacts: [CNContact] = []
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - User Actions
#IBAction func contactListPressed(_ sender: AnyObject) {
let contactPickerViewController = CNContactPickerViewController()
contactPickerViewController.delegate = self
present(contactPickerViewController, animated: true, completion: nil)
}
#IBAction func addContactPressed(_ sender: AnyObject) {
let newContact = CNMutableContact()
newContact.givenName = "Apps"
newContact.familyName = "Foundations"
newContact.nickname = "AF"
if let image = UIImage(named: "logo-apps-foundation.jpg"),
let data = UIImagePNGRepresentation(image){
newContact.imageData = data
}
let phone = CNLabeledValue(label: CNLabelWork, value: CNPhoneNumber(stringValue: "+441234567890"))
newContact.phoneNumbers = [phone]
let email = "" //Your Input goes here
let Email = CNLabeledValue(label:CNLabelWork, value: email as NSString)
newContact.emailAddresses = [Email]
newContact.jobTitle = "Apps Foundation"
newContact.organizationName = "Apps Foundation"
newContact.departmentName = "IT"
let facebookProfile = CNLabeledValue(label: "Facebook", value: CNSocialProfile(urlString: "https://www.facebook.com/appsfoundation", username: "AppsFoundation", userIdentifier: "appsfoundation", service: CNSocialProfileServiceFacebook))
let twitterProfile = CNLabeledValue(label: "Twitter", value: CNSocialProfile(urlString: "https://twitter.com/AppsFoundation", username: "AppsFoundation", userIdentifier: "appsfoundation", service: CNSocialProfileServiceTwitter))
newContact.socialProfiles = [facebookProfile, twitterProfile]
let skypeProfile = CNLabeledValue(label: "Skype", value: CNInstantMessageAddress(username: "AppsFoundation", service: CNInstantMessageServiceSkype))
newContact.instantMessageAddresses = [skypeProfile]
var birthday = DateComponents()
birthday.year = 1991
birthday.month = 1
birthday.day = 1
newContact.birthday = birthday
let request = CNSaveRequest()
request.add(newContact, toContainerWithIdentifier: nil)
do {
try store.execute(request)
let alert = UIAlertController(title: "Contacts iOS 9", message: "New contact has been created", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
} catch let error{
print(error)
}
}
#IBAction func textFieldValueChanged(_ sender: AnyObject) {
if let query = textField.text {
findContactsWithName(query)
}
}
//MARK: - Private Methods
func findContactsWithName(_ name: String) {
AppDelegate.sharedDelegate().checkAccessStatus({ (accessGranted) -> Void in
if accessGranted {
DispatchQueue.main.async(execute: { () -> Void in
do {
let predicate: NSPredicate = CNContact.predicateForContacts(matchingName: name)
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactBirthdayKey, CNContactViewController.descriptorForRequiredKeys()] as [Any]
self.contacts = try self.store.unifiedContacts(matching: predicate, keysToFetch:keysToFetch as! [CNKeyDescriptor])
self.tableView.reloadData()
}
catch {
print("Unable to refetch the selected contact.")
}
})
}
})
}
func updateContact(_ contactIdentifier: String) {
do {
let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactBirthdayKey, CNContactPhoneNumbersKey, CNContactViewController.descriptorForRequiredKeys()] as [Any]
let contact = try store.unifiedContact(withIdentifier: contactIdentifier, keysToFetch:keysToFetch as! [CNKeyDescriptor])
let contactToUpdate = contact.mutableCopy() as! CNMutableContact
contactToUpdate.phoneNumbers = [CNLabeledValue(label: CNLabelWork, value: CNPhoneNumber(stringValue: "+440987654321"))]
let saveRequest = CNSaveRequest()
saveRequest.update(contactToUpdate)
try store.execute(saveRequest)
} catch let error{
print(error)
}
}
}
//MARK: - UITableViewDataSource
extension MainViewController: CNContactPickerDelegate {
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let selectedContactID = contact.identifier
updateContact(selectedContactID)
}
}
//MARK: - UITableViewDataSource
extension MainViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let CellIdentifier = "MyCell"
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier)
cell!.textLabel!.text = contacts[indexPath.row].givenName + " " + contacts[indexPath.row].familyName
if let birthday = contacts[indexPath.row].birthday {
let formatter = DateFormatter()
formatter.dateStyle = DateFormatter.Style.long
formatter.timeStyle = .none
cell!.detailTextLabel?.text = formatter.string(from: ((birthday as NSDateComponents).date)!)
}
return cell!
}
}
//MARK: - UITableViewDelegate
extension MainViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let controller = CNContactViewController(for: contacts[indexPath.row])
controller.contactStore = self.store
controller.allowsEditing = false
self.navigationController?.pushViewController(controller, animated: true)
}
}
I know I need to incorporate something like this but I am not sure where or how to set the JSON data to a variable or the correct type and then incorporate code of this type
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let controller = segue.destination as! ViewControllerB
controller.selectedName = objects[indexPath.row]
}
}
}
sorry for the awful explanation. any help possible would be appreciated, I have been struggling for quite some time.
First of all, you need to have the other view controller that you are trying to pass data to. It can either be on the Interface Builder or done programmatically (I'll assume it's on the IB for now). Then you'll need to setup a segue between the Main VC and the Details VC and give it an identifier e.g. showDetail.
Next would be to determine the data that Details VC needs for it to work properly. You can have individual variables for each data item (e.g. name, age, phone, email, etc) but usually if there is a lot of info, it's best to use a data model. In your case, since you are trying to display contact info, you can simply reuse CNContact.
So you simply need a CNContact in your Details VC that you'll set before transitioning from Main VC in the prepareForSegue function. And to initiate the segue, all you have to do is call performSegue function.
Hope that at least gives you some direction

swift - dismissing mail view controller from Sprite Kit

I am trying to add a send email button to a Sprite Kit game. I can get the email dialog to show up. But if I hit cancel, the app will crash or do nothing. If I hit send, the email will send, but the dialog stays. I cannot get the mailComposeController function to fire...please help!
Code:
import Foundation
import UIKit
import MessageUI
class MailViewController: UIViewController, MFMailComposeViewControllerDelegate {
let systemVersion = UIDevice.currentDevice().systemVersion
let devicemodel = UIDevice.currentDevice().model
let appVersion = NSBundle.mainBundle().infoDictionary?["CFBundleShortVersionString"] as! String
let appBuild = NSBundle.mainBundle().infoDictionary?["CFBundleVersion"] as! String
let myrootview2 = UIApplication.sharedApplication().keyWindow?.rootViewController
let mailComposerVC = MFMailComposeViewController()
override func viewDidLoad() {
super.viewDidLoad()
}
func sendEmailButtonTapped(sender: AnyObject) {
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.view.window?.rootViewController = mailComposerVC
print("This is the rootview2: \(myrootview2)")
myrootview2!.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert()
}
}
func configuredMailComposeViewController() -> MFMailComposeViewController {
var msgbody: String
mailComposerVC.mailComposeDelegate = self
msgbody = "\n\nDevice: \(devicemodel)\niOS Version: \(systemVersion)\nApp Version: \(appVersion)\nApp Build Number: \(appBuild)\n"
mailComposerVC.setToRecipients(["test1#test.com"])
mailComposerVC.setSubject("test subject")
mailComposerVC.setMessageBody(msgbody, isHTML: false)
//print(mailComposerVC)
return mailComposerVC
}
func showSendMailErrorAlert() {
let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail. Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
sendMailErrorAlert.show()
}
// THIS DOESN'T GET CALLED WHEN SENDING OR CANCELLING EMAIL!
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
let test1 = result.rawValue
print(test1)
print(controller)
print(self)
print(myrootview2)
}
The issue is you are making the mailVC as the root view, you have to present it on your view like given below
#IBAction func sendEmailButtonTapped(sender: AnyObject) {
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert()
}
}
 func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
controller.dismissViewControllerAnimated(true, completion: nil)
}