How to use MFMailComposeViewController with simulator? - ios-simulator

I want to use a MFMailComposeViewController() in Swift 2.0 and Xcode 7.0.1. I fear, this is a duplicate question, but didn't find a solution.
My code:
#IBAction func sendMail(sender: AnyObject) {
let picker = MFMailComposeViewController()
picker.mailComposeDelegate = self
picker.setSubject(subject.text!)
picker.setMessageBody(body.text, isHTML: true)
presentViewController(picker, animated: true, completion: nil)
}
and
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
switch result.rawValue {
case MFMailComposeResultCancelled.rawValue:
print("Mail cancelled")
case MFMailComposeResultSaved.rawValue:
print("Mail saved")
case MFMailComposeResultSent.rawValue:
print("Mail sent")
case MFMailComposeResultFailed.rawValue:
print("Mail sent failure: \(error!.localizedDescription)")
default:
break
}
dismissViewControllerAnimated(true, completion: nil)
The error is thrown in
print("Mail cancelled")
When I test this on my device (iPad) everything is fine. But when i use the simulator, i get the error
viewServiceDidTerminateWithError: Error
Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)"
UserInfo={Message=Service Connection Interrupted}
Mail cancelled
But I want to run this in simulator to see, how it looks i.e. on iPhone 6...

You have to first check if the device can send mail with MFMailComposeViewController.canSendMail()
#IBAction func sendMail(sender: AnyObject) {
guard MFMailComposeViewController.canSendMail()
else {
// TODO: Alert the user its device cannot send mail
print("Mail services are not available")
return
}
let picker = MFMailComposeViewController()
picker.mailComposeDelegate = self
picker.setSubject(subject.text!)
picker.setMessageBody(body.text, isHTML: true)
presentViewController(picker, animated: true, completion: nil)
}

Related

watchOS 8 HealtKit background delivery stops working after a few hours in background

I'm trying to implement background delivery of the HealthKit data for an independent watchOS 8 app. I was following Gettings the most out of HealthKit WWDC talk and seems to have added everything that is needed for background delivery to work, including recent iOS 15 and watchOS 8
com.apple.developer.healthkit.background-delivery entitlement. But for some reason, background delivery stops working after approximately 3-5 hours after the app went to the background. For example, I'm receiving updates during the evening from the app, but then over the night updates stops delivering and I'm getting those only if I open the app again in the morning. See the ExtensionDelegate code below
class ExtensionDelegate: NSObject, WKExtensionDelegate {
private let healthStore = HKHealthStore()
private var anchor: HKQueryAnchor?
func applicationDidFinishLaunching() {
print("application did finish launching")
activateHeathKit()
}
func activateHeathKit() {
let types = Set([HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent)!])
healthStore.requestAuthorization(toShare: nil, read: types) { [weak self] success, _ in
guard let `self` = self else {
return
}
guard let lowHeartRateType = HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent) else {
return
}
`self`.healthStore.enableBackgroundDelivery(for: lowHeartRateType, frequency: .immediate) { success, _ in
print("enableBackgroundDelivery: \(success) for lowHeartRateEvent")
}
let query = HKObserverQuery(sampleType: stepsType, predicate: nil) { _, completionHandler, error in
`self`.updateLowHeartRate {
completionHandler()
}
}
`self`.healthStore.execute(query)
}
}
func updateLowHeartRate(completionHandler: #escaping () -> Void) {
guard let lowHeartRateType = HKObjectType.categoryType(forIdentifier: .lowHeartRateEvent) else {return}
let anchoredQuery = HKAnchoredObjectQuery(type: lowHeartRateType, predicate: nil, anchor:
self.anchor, limit: Int(HKObjectQueryNoLimit)) { [unowned self] query, newSamples,
_, newAnchor, error -> Void in
for item in newSamples ?? [] {
let date = item.startDate
let hour = Calendar.current.component(.hour, from: date)
let minute = Calendar.current.component(.minute, from: date)
let message = "Low heart rate from \(hour):\(String(format: "%02d", minute))"
print(message)
}
self.anchor = newAnchor
completionHandler()
}
healthStore.execute(anchoredQuery)
}
}
I don't see an implementation of the handle(_:) method for background tasks but perhaps it is just not shown. Link to the docs here.
Just in case here is how I have my workout app set up to update complications on the watch face.
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
if WKExtension.shared().applicationState == .background {
if let watchComplication = task as? WKApplicationRefreshBackgroundTask {
// do background work here
}
}
task.setTaskCompletedWithSnapshot(false)
}
completePendingTasksIfNeeded()
}

UpdateApplicationContext Not Being Called

It's my first time trying WatchConnectivity kit. When I attempt to send ApplicationContext from phone, my debug print indicates that updateApplicationContext is working correctly (with isPaired/reachable/isActivated/isWatchAppInstalled checked before sending the update). However, I'm having trouble receiving it form the paired watch simulator as nothing is shown on that end. Here are the code for the Phone Extension:
func sendDataToWatch(data: String) {
state.append(data)
do {
NSLog("sent by phone")
try WCSession.default.updateApplicationContext(["currentstate": state])
}
catch {
print(error)
}
}
Here are the code for watch:
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
print("Hello")
if let getState = applicationContext["currentstate"] as? [String]{
print("\(getState)")
self.state = getState[0]
}
}
Any suggestion would be appreciated!

Problem with WatchConnectivity transferFile (watch -> iphone)

I want to send a file created on the watch to the iOS companion-app, using WatchConnectivity and a setup WCSession, but it does not get through to the iPhone. When I use send a message containing a Dictionary in stead, the data does get to the iPhone.
Both AppDelegate and ExtensionDelegate uses NotificationCenter to communicate with ViewController and ExtensionController. They are left out for simplicity, but the notifications work fine.
iOS' AppDelegate.swift
extension AppDelegate: WCSessionDelegate {
// 1
func sessionDidBecomeInactive(_ session: WCSession) {
print("WC Session did become inactive")
}
// 2
func sessionDidDeactivate(_ session: WCSession) {
print("WC Session did deactivate")
WCSession.default.activate()
}
// 3
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: \(error.localizedDescription)")
return
}
print("WC Session activated with state: \(activationState.rawValue)")
}
func setupWatchConnectivity() {
// 1
if WCSession.isSupported() {
// 2
let session = WCSession.default
// 3
session.delegate = self
// 4
session.activate()
}
}
func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) {
print("didFinish fileTransfer")
}
func session(_ session: WCSession, didReceive file: WCSessionFile) {
print("didReceive file")
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) { // 2
print("didReceiveMessage reached")
}
}
On the watch I have:
ExtensionDelegate.swift
extension ExtensionDelegate: WCSessionDelegate {
func setupWatchConnectivity() {
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
func session(_ session: WCSession, activationDidCompleteWith
activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: " +
"\(error.localizedDescription)")
return
}
print("WC Session activated with state: " +
"\(activationState.rawValue)")
}
func sendFileToPhone(_ notification:Notification) {
// 1
if WCSession.isSupported() && WCSession.default.isReachable {
// 2
if let fileURL = notification.object as? URL {
print("Sending file for URL: \(fileURL.absoluteURL.absoluteString)")
WCSession.default.transferFile(fileURL, metadata: nil) // <- if I use sendMessage here stuff works...
}
}
}
func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) {
// handle filed transfer completion
print("File transfer complete")
print("Outstanding file transfers: \(WCSession.default.outstandingFileTransfers)")
print("Has content pending: \(WCSession.default.hasContentPending)")
}
func session(_session: WCSession, didFinishFileTransfer fileTransfer: WCSessionFileTransfer, error: NSError?) {
print("error: ", error as Any)
}
}
After the file is sent from the Watch I inspect, as you see, outstandingFileTransfers and hasContentPending properties of the WCSession, and they indicate that the file should have been transferred, but my :didReceive: method of my AppDelegate extension doesn't get called. Again, when I use sendMessage, the AppDelegate's :didReceiveMessage: is invoked as expected.
What am I missing?
Note: I've tried to run this Apple's demo project that features the different communication methods, and I experience the same thing: transferFile does not trigger anything on counterpart.
It seems to be a bug of iOS 13/watchOS 6 simulator. I've tried to change Xcode simulators to iOS 12 and watchOS 5, how it's suggested in this thread and the issue disappeared.

Trigger an alert when using Firebase observer and events

Working on an app that has two parts - Rider & Driver. When the driver accepts the request, an alert is then sent to the rider that the request was accepted and driver is on the way.
Unable to trigger the alert to the rider.
RiderVC:
func driverAcceptedRequest(requestAccepted: Bool, driverName: String) {
if !riderCancelledRequest {
if requestAccepted {
self.alertTheUser(title: "Ryde Accepted", message: "\(driverName) Has Accepted Your Ryde Request and will message you with details")
} else {
RydeHandler.Instance.cancelRyde()
alertTheUser(title: "Ryde Cancelled", message: "\(driverName) Has Cancelled the Ryde Request")
}
}
riderCancelledRequest = false
}
RydeHandler.swift:
// DRIVER ACCEPTED RYDE
DataService.Instance.requestAcceptedRef.observe(FIRDataEventType.childAdded) { (snapshot: FIRDataSnapshot) in
if let data = snapshot.value as? NSDictionary {
if let name = data[Constants.NAME] as? String {
if self.driver == "" {
self.driver = name
self.delegate?.driverAcceptedRequest(requestAccepted: true, driverName: self.driver)
}
}
}
}
Firebase database structure:
Edit
ViewDidLoad in tableviewcontroller - list of requests:
ref.child("drivers").child("RideRequests").observe(FIRDataEventType.value, with: { snapshot in
self.rideRequests.removeAll()
for item in snapshot.children{
self.rideRequests.append(item as! FIRDataSnapshot)
}
self.rideRequests.reverse()
self.tableView.reloadData()
})

Proper way of determining if there is connection to a firebase database

func checkForConnection() {
let connectedRef = FIRDatabase.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
if let connected = snapshot.value as? Bool , connected {
print("Camera View Connected")
self.connectionStatus = 1
self.UpdateConnection()
} else {
print("Camera View Not connected")
self.connectionStatus = 0
Timer.scheduledTimer(timeInterval: TimeInterval(2.0), target: self, selector: #selector(CameraViewController.UpdateConnection), userInfo: nil, repeats: false)
}
})
}
func UpdateConnection() {
let connectionBanner = Banner(title: "Connection Error", subtitle: "Tap to dismiss", image: UIImage(named: "Wi-Fi"), backgroundColor: UIColor(hexString: "#DA4167"))
if connectionStatus == 0 {
print("connection banner shown from camera view")
// What happens when there's no connection
connectionBanner.dismissesOnTap = true
connectionBanner.show(duration: 10)
}
else if connectionStatus == 1 {
connectionBanner.dismiss()
}
}
Here I have a code which detects if the app has connection to the database. If it doesn't, it will display a banner notification telling the user that he has lost connection. But on first install, it always displays no connection despite having connection.
I understand the app takes awhile to establish connection but is there a way to implement a retry before calling the banner? I've already set it such that even when there's no connection, it will delay before executing the banner if connectionState is still = 0.
Thanks a lot!
Edit:
Here's my viewDidLoad:
override func viewDidLoad() {
// for connection
checkForConnection()
}
There's only one function here to check for connection.

Resources