Pass saved Workout from Watch to iPhone - swift

On Watch, I'm able to pass a saved workout from the WorkoutInterfaceController to the SummaryInterfaceController. But I was wondering how to pass the saved workout from the Watch to the iPhone (so I can display it in a Summary View Controller too).
Do you know? Or is there a better way I'm supposed to do this?
Here's what I use to pass the saved workout from WorkoutInterfaceController to the SummaryInterfaceController:
private func saveWorkout() {
// Create and save a workout sample
let configuration = workoutSession!.workoutConfiguration
let isIndoor = (configuration.locationType == .indoor) as NSNumber
print("locationType: \(configuration)")
let workout = HKWorkout(activityType: configuration.activityType,
start: workoutStartDate!,
end: workoutEndDate!,
workoutEvents: workoutEvents,
totalEnergyBurned: totalEnergyBurned,
totalDistance: totalDistance,
metadata: [HKMetadataKeyIndoorWorkout:isIndoor]);
healthStore.save(workout) { success, _ in
if success {
self.addSamples(toWorkout: workout)
}
}
WKInterfaceController.reloadRootControllers(withNames: ["SummaryInterfaceController"], contexts: [workout])
}
private func addSamples(toWorkout workout: HKWorkout) {
// Create energy and distance samples
let totalEnergyBurnedSample = HKQuantitySample(type: HKQuantityType.activeEnergyBurned(),
quantity: totalEnergyBurned,
start: workoutStartDate!,
end: workoutEndDate!)
// Add samples to workout
healthStore.add([totalEnergyBurnedSample], to: workout) { (success: Bool, error: Error?) in
if success {
// Samples have been added
}
}
}
Let me know if any questions or information needed, thanks!

As a part of my research and development,I discovered how the pairing of the iPhone and the Apple Watch has the potential to be useful.
In this case, tapping on a button on the Watch app will send text on the iPhone.
To make a simple demo of this functionality, place a button on the WatchKit interface and a label on the iOS app’s storyboard.
Now, hook up the button to the WatchKit Interface Controller as an IBAction in order to respond to button tap events. Also hook up the Label to the UI View Controller as an IBOutlet.
In the Interface Controller, we make up a string variable to send to the label and in the button’s IBAction method, make a dictionary that includes the string variable you made. This dictionary is what is passed to the iPhone app.
class InterfaceController: WKInterfaceController {
Var str: String = "Hello Phone"
#IBAction func button() {
let dict: Dictionary = ["message": str]
}
Use the following method to send the dictionary to the iPhone.
WKInterfaceController.openParentApplication(dict, reply: {(reply, error) -> void in
print("Reply receive from iPhone app")
})
In the AppDelegate of the iOS app, add the following application method. This is what will handle the previous methods communication from the Watch. Also we can use a notification to notify a view controller that data has been received and to pass it along.
func application(application: UIApplication, handleWatchkitExtensionRequest userInfo: [NSObject : AnyObject]?, reply:(([NSObject : AnyObject]!) {
NSNotificationCenter.defaultCenter().postNotificationName("WatchKitReq", object: userInfo)
}
Finally in the view controller, make a listener for the notification that will update the label’s text to the string that was sent with the data.
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector("handleWatchKitNotification:"), name: "WatchKitReq", object: nil)
}
func handleWatchKitNotification(notification: NSNotification) {
if let userInfo = notification.object as? [String : String] {
label.text = userInfo["message"]
}
}
Hope this will help you to understand. For more concerns you can look on this,
Delegate Method

To do this you have to create an App Group which is essentially a space which both apps can use. It was brought in with the exetension framework in iOS8 so apps can communicate with their Today widgets, or custom keyboards, and amongst other applications.
ADD CAPABILITIES
The first thing we have to do is add the app group capability to both our iPhone and Watch Watch Extension targets.
To do this open up the project settings (blue icon at the top of the list of files) and select the iPhone target. You will need to select the “capabilities” tab at the top of the page and scroll down to turn on app groups.
This requires a connected developer profile, and will take a while to enable. You’ll need to do the same steps to switch on app groups for the watch kit extension also.
Next you need to ensure that the app group string is an identifier string you want and that makes sense for your app, it must start with the word group or it complains. You can also add multiple groups if you wish. Whatever you pick they must be enabled with a blue tick (again this might take a while) and are exactly the same for both the iPhone and Watch extension targets!
To use App Groups, it’s not that different or difficult to use than NSUserDefaults:
var userDefaults = NSUserDefaults(suiteName: "group.com.example.My-App")
userDefaults.setObject(true, forKey: "isDarkModeEnabled")
userDefaults.synchronize()
The only differences here are how NSUserDefaults is instantiated and calling synchronize at the end. You feed it the container ID to the constructor parameter called “suiteName”, then call “synchronize()”, and your data flies to the cloud for other apps and devices to consume.
Taking It to the Next Level
You can take this one step further by creating a class for your app and abstract the underlying storage for your properties. Here’s an example:
public class ConfigurationModel: NSObject {
public static let storageKey = "group.com.example.My-App"
public let userStorage = NSUserDefaults(suiteName: storageKey)
public var isDarkModeEnabled: Bool {
 get {
// Get setting from storage or default
if userStorage?.objectForKey("isDarkModeEnabled") == nil {
userStorage?.setObject(false, forKey: "isDarkModeEnabled")
userStorage?.synchronize()
}
return userStorage?.objectForKey("isDarkModeEnabled")
}
set {
// Set new value in storage
userStorage?.setObject(newValue, forKey: "isDarkModeEnabled")
userStorage?.synchronize()
}
}
At the top of the class, I am declaring my group container ID and creating the NSUserDefault object out of it. Then my properties for the class have getters and setters to store the data to the App Group. If the key doesn’t exist, it creates it with a default value and synchronizes it. Using the class from this point forward is simple:
var configModel = ConfigurationModel()
configModel.isDarkModeEnabled = true
This property is stored in the cloud! Everything is abstracted away for you. You don’t have to be bothered about storing and synchronizing it into the App Group. It’s all done automatically for you in the getters and setters!
Hope, this will help you to understand how you can share data between the iPhone and Apple Watch app.

Related

Setting Observer for Swift Objects/Properties

I've been looking for a way to trigger a method when the number of displays connected to a mac changes. I know I can get the value of NSScreen.screens.count, but I need to find a way to create a notification or something when that value changes or something else that would indicate a change in the number of displays connected.
I have tried KVO examples here and here, but in order for either of those to work there needs to be an event that triggers the methods inside the class.
In essence this is what I would like to do based on the first link:
class EventObserverDemo {
var statusObserver: NSKeyValueObservation?
var objectToObserve: NSScreen.screens.count?
func registerAddObserver() -> Void {
statusObserver = objectToObserve?.observe(NSScreen.screens.count, options: [.new, .old], changeHandler: {[weak self] (NSScreen.screens.count, change) in
if let value = change.newValue {
// observed changed value and do the task here on change.
print("The display count has changed.")
}
})
}
func unregisterObserver() -> Void {
if let sObserver = statusObserver {
sObserver.invalidate()
statusObserver = nil
}
}
}
I tried using a notification that used NSScreen.colorSpaceDidChangeNotification but that does not trigger a notification if a display is disconnected.
I would like to find a way to detect any time an external display is connected or disconnected. There has to be something I haven't found yet because whenever I plug in an external display to my mac I see the screen on the main display change, so there's some kind of notification that something changed whether I plug in a display or unplug it from my mac.
I looked at the didSet function, but I couldn't figure out a way to implement that on NSScreen.screens.count property.
I also looked into a property wrapper for NSScreen.screens.count but again I couldn't figure that out either.
You can observe the NSApplication.didChangeScreenParametersNotification notification. This example will only print once each time a display is either connected or disconnected, and what the change was in the number of screens.
Code:
class EventObserverDemo {
var lastCount = NSScreen.screens.count
init() {
NotificationCenter.default.addObserver(
self,
selector: #selector(trigger),
name: NSApplication.didChangeScreenParametersNotification,
object: nil
)
}
#objc private func trigger(notification: NSNotification) {
let newCount = NSScreen.screens.count
if newCount != lastCount {
print("Switched from \(lastCount) to \(newCount) displays")
lastCount = newCount
}
}
}
You don't need to remove/invalidate the observer either, easier to let the system handle it:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you do not need to unregister an observer that you created with this function. If you forget or are unable to remove an observer, the system cleans up the next time it would have posted to it.

Passing Data Indirectly to an Unrelated NSViewController

I am new to macOS Development and I am working on a project for macOS using Xcode 10 and Swift 4.2.
The project contains mainly 3 view controllers.
Firstly, ViewController (the main welcome screen which separates the other two) has two buttons to call the other two respectively.
Secondly, MakeEntry View Controller creates an array of strings data variable using a form type structure comprised of text views and save button etc. which in the end just saves all input data into an array of strings data variable called carrierArray
Thirdly, there is a split view controller for displaying two children view controller namely EntryList and EntryDetail
EntryList (the left pane) contains a Table View to display titles of entries and EntryDetail (the right pane) will contain the description of the title entry (somewhat like the default notes app of macOS)
I want to achieve a simple functionality of being able to access or read that Array of strings variable called carrierArray which is created when the MakeEntry view controller saves it into a global variable defined within its own class file But I want to access that array of strings anywhere and anytime later.
I cannot use delegates and protocols, closures, segues or storyboard identifiers to carry that data because I am not navigating to the Split View Controller straightaway and also because I want to store that data
to manipulate it further before displaying it in the right pane of split view controller (EntryDetail) .
I am unable to figure out whether how it might be possible to achieve this functionality using NSUserDefaults or CoreData.
Therefore I tried using the Notification Centre after storing that array of Strings in a Dictionary namely notifDictionary containing a key called carryData to be stored as the data object of notification centre And with some research and some trials and errors but without any luck all resulting in failure to get that data in the split view controller left pane class file namely (EntryDetail).
Code Snippets are as below, thanks a lot in advance for the kind help.
In MakeEntry View controller:
notifDictionary = ["carryData": carrierArray]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "dataCarrier") , object: nil, userInfo: notifDictionary)
In EntryList View Controller:
(Tried using both types of selector methods one at a time and even using them together but all without luck! Please Help!)
The Variable datumDict and datumArray and nothing but copy receivers for carrierArray and notifDictionary
var datumDict: [String:[String]] = [:]
var datumArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.getThatDict(_:)), name: NSNotification.Name(rawValue: "dataCarrier") , object: nil)
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "dataCarrier") , object: nil, queue: nil, using: catchNotification)
//datumArray = datumDict["carryData"]!
}
#objc func onNotification(notification:Notification)
{
print(notification.userInfo!)
}
func catchNotification(notification: Notification) -> Void
{
let theDict = notification.object as! NSDictionary
datumDict = theDict as! [String: [String]]
guard let theData = notification.userInfo!["carryData"] as? [String:[String]] else { return }
datumDict = theData
}
#objc func getThatDict(_ notification: NSNotification)
{
print(notification.userInfo ?? "")
if let dict = notification.userInfo as NSDictionary?
{
if let thatDict = dict["carryData"] as? [String: [String]]
{
datumDict = thatDict
}
}
}
With the caveat that "globals and singletons should be avoided," it sounds like they are a good fit for what you're trying to do. As you get more comfortable in Cocoa you can move into more sophisticated means of accomplishing this (dependency injection). Look into this as you get more comfortable with Cocoa.
Create a simple singleton type:
// AppState.swift
class AppState {
// Basic singleton setup:
static let shared = AppState()
private init() {} // private prevents instantiating it elsewhere
// Shared state:
var carrierArray: [String] = []
}
Access it from your view controllers:
// YourViewController.swift:
#IBAction func doSomething(_ sender: Any) {
AppState.shared.carrierArray = ...
}
If you need to update the other view controllers when this shared state changes, notifications are a good tool for that. You could do this with a didSet on carrierArray, or simply trigger the notification manually.

strange optional error in Swift

Okay, so basically I am following a tutorial on udemy on how to create a chat with Backendless and Firebase. However, I prefer not to use Backendless, because I don't want to rely on 2 providers - so I want to stick to Firebase only. Therefore, I am currently converting my code to Firebase.
I have a view controller that displays a unique page for each UID - from a database that I have. The UID is stored as a String, and is assigned upon a segue from another table view controller (this works fine). After that, I fetch the data that I want from the user, with the UID. I have a "Start Chat" button that is supposed to create a new chat.
In this tutorial, the tutor has set a protocol (delegate) that is triggering another function from another view controller. This is what it looks like:
protocol ChooseUserDelegate {
func createChatroom(withUser: String)
}
var delegate: ChooseUserDelegate!
and in my chat #IBAction, I have this code:
#IBAction func StartChat(sender: AnyObject) {
let userID = uid
if let theId = userID as? String {
delegate.createChat(String(theId))
}
}
(The code above is all in the same VC.).
In another view controller, where the createChat() function is stored, is the following code:
class AnotherVC UIViewController, UITableViewDelegate, UITableViewDataSource, ChooseUserDelegate{
func createChat(withUser: String) {
print(withUser)
}
}
The problem is that I can't get to call createChat(), because of an optional error (unwrapping) on the delegate.createChat(String(theId)).
Edit: Even with a "" input, I get an error. I am really confused now. Is it something wrong with my delegate?
The only part of your code that is optional is delegate (because you correctly unwrapped userID). Therefore, the error must be due to delegate being nil. Make sure that you set delegate before calling StartChat().
The line var delegate: ChooseUserDelegate! does not initialize a delegate. When you write ChooseUserDelegate! you are only defining the type of the delegate variable. It is automatically set to nil. To initialize a new instance of ChooseUserDelegate you would need to write something like:
var delegate: ChooseUserDelegate! = ChooseUserDelegate()
There are a few other ways you could clean up your code. Method names should be llamaCase, not CamelCase, so you should rename StartChat() to startChat() (be sure to reconnect in interface builder). The body of that method has three different names for the same variable, uid. See how simple it could be:
#IBAction func startChat(sender: AnyObject) {
if let uid = uid as? String {
delegate.createChat(uid)
}
}
if let theId = userID {
delegate.createChat(String(theId))
}

How to use an NSAlert with storyboards

I'm teaching myself Swift (currently using Xcode 7.3) and I'm working with storyboards for the first time. I'm writing an OS X-based app and I want to display an alert when the user attempts to load data when data already exists. I've read the following thread, Add completion handler to presentViewControllerAsSheet but I'm having trouble wrapping my head around closures/completion handlers. I understand them "in theory" but not yet well enough to write one.
In the thread above, a Struct is being returned. I just need to return an Int or Bool to indicate whether the user wants to overwrite the data or not.
You don't need to create a second view controller. Just configure and display an NSAlert object:
#IBAction func loadData(sender : AnyObject) {
let dataAlreadyExists = true // assume this is always true
if dataAlreadyExists {
let alert = NSAlert()
alert.messageText = "Do you want to reload data?"
alert.addButtonWithTitle("Reload")
alert.addButtonWithTitle("Do not reload")
alert.beginSheetModalForWindow(self.view.window!) { response in
if response == NSAlertFirstButtonReturn {
// reload data
}
}
}
}

Passing Data with Delegates

Im having a hard time understanding data passing with delegates, I have looked but I get confused by the answers as I'm usually trying to do the opposite of what needs to be done for mine.
What I am trying to do is pass a PFObject from my main viewController (a UITableView) to a PopOverViewController.
I have done this successfully, but I need to pass the object. What would be the best way to pass from the mainController (SOITableViewController) to the popover (DetailPopViewController)?
Where should the protocol go? Where should the Delegate method be placed, etc.
Thank you!
PrepareForSegue, NSUserDefault and Singleton
You have a few possible options to pass your data to other views depending how you want that data to be handled, I will explain each for you and you can choose which one best fit your need.
prepareForSegue: Method
I recommend this method if you want to hold your data for 1 segue transition, it's a good cause to pass this again to another view afterward you need to create another prepareForSegue within the new view. here is an example on how to do this:
First, you create 2 variables in both views, 1 to send (currentViewController.swift) and 1 to receive (toViewyourGoingController.swift).
currentViewController.swift var dataToSend: AnyObject?
ViewYourGoingController.swift var dataToReceive: AnyObject?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//Check your segue, this way you can transfer different data to different view. also make sure the identifier match your segue.
if segue.identifier == "toViewYourGoing" {
//Initial your second view data control
let ExchangeViewData = segue.destinationViewController as! toViewyourGoingController
//Send your data with segue
ExchangeViewData.dataToReceive = dataToSend
}
}
NSUserDefault
Now this method is good if you want to keep your data live as long as the app is installed, once the app is removed this will reset automatically. You also have the option to update the value of the key if you wish, here is how you do NSUserDefault:
I always like to register my NSUserDeafult to default setting, a lot of people just continue with the second step without registering.
Register NSUserDefault in AppDelgate.swift
NSUserDefaults.standardUserDefaults().registerDefaults(["valueName": AnyObject])
Set Value to your NSUserDefault, this depends on what type of data you're storing, should match the one with your registration if you did register. (Example of Boolean data type below)
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "valueName") //Bool Data Type
Make sure you synchronize once you set the value to the NSUserDefault, this way it will update instantly, otherwise it will update when it get a chance.
NSUserDefaults.standardUserDefaults().synchronize()
Receive Value: this will receive boolean value since we set boolean and register boolean.
let Variable: Bool! = NSUserDefaults.standardUserDefaults().boolForKey("valueName")
Singleton
Now singleton is basically a global variable that you can use them in any views, but some developers experience some bugs and difficulties, use it at your own risk, I recommend this method when you're definite that you will use that data a lot (STILL RISKY), but this method is like goddess of data handling :).
Create a NSObject subclass and call it DataManager.swift (I call it data manager cause it handle data.) as following:
import UIKit
class DataManager: NSObject {
//Store Data Globally
static var someData: Boo! //This Boolean, you can choose whatever you want.
}
the static is what keep your data live.
Now you can store and receive someData from anywhere like you handle any data type like this.
//Store
DataManager.someData = true
//Receive
print(DataManager.someData)
Challenges:
You can also use
Keychain
Sergey Kargopolov will walk you through how to use a third party to use swift keychain. Otherwise, you can take even harder challenge and create one yourself :P .
Key-Value Data in iCloud
The best way to do this would be to pass it over when you do the prepare for segue method. So to do this make a variable in your detailPopViewController. In this case your pop over segue in storyboard will have the segue identifier detailView. Also tblSearchResults is your tableView outlet (you can name it whatever you want). Is that what you were looking for?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailView" {
if let detailViewController = segue.destinationViewController as? PopOverViewController{
if let indexPath = self.tblSearchResults.indexPathForCell(sender as! SOITTableViewController) {
detailViewController.objectInSecondVC = objectFromFirstVC[indexPath.row]
}
}
}
}