I have initialized the credentials provider per this AWS Developer Guide. I'm not sure if it worked, and how to check. I can't seem to find any documentation on how to use Cognito with Swift. I'm running it as a unit test, and the test passes and the line print("identityId", identityId) outputs:
identityId <AWSTask: 0x17d5fde0; completed = NO; cancelled = NO; faulted = NO;>
However, during debug the property identityProvider.identityId is nil.
Here are my files:
// MyAuth.swift
import Foundation
import AWSCognito
class MyAuth {
func getUnauthCognitoId()->Bool {
let identityProvider = MyIdentityProvider()
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityProvider: identityProvider, unauthRoleArn: Constants.ARNUnauth.value, authRoleArn: Constants.ARNAuth.value)
let defaultServiceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
if let identityId = identityProvider.getIdentityId() {
print("identityId", identityId)
return true
} else {
return false
}
}
}
And
// MyIdentityProvider.swift
import Foundation
import AWSCognito
class MyIdentityProvider: AWSAbstractCognitoIdentityProvider {
var _token: String!
var _logins: [ NSObject : AnyObject ]!
// Header stuff you may not need but I use for auth with my server
/*let acceptHeader = "application/vnd.exampleapp-api+json;version=1;"
let authHeader = "Token token="
let userDefaults = NSUserDefaults.standardUserDefaults()
let authToken = self.userDefaults.valueForKey("authentication_token") as String*/
// End point that my server gives amazon identityId and tokens to authorized users
let url = "https://api.myapp.com/api/amazon_id/"
func authenticatedWithProvider()->Bool {
if let logins = _logins {
return logins["ProviderName"] == nil
}
else {
return false
}
}
override var token: String {
get {
return _token
}
}
override var logins: [ NSObject : AnyObject ]! {
get {
return _logins
}
set {
_logins = newValue
}
}
override func getIdentityId() -> AWSTask! {
if self.identityId != nil {
return AWSTask(result: self.identityId)
}
else if(!self.authenticatedWithProvider()) {
return super.getIdentityId()
}
else{
return AWSTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
if self.identityId == nil {
return self.refresh()
}
return AWSTask(result: self.identityId)
})
}
}
override func refresh() -> AWSTask! {
let task = AWSTaskCompletionSource()
if(!self.authenticatedWithProvider()) {
return super.getIdentityId()
}
else {
// TODO: Authenticate with developer
return task.task
}
/*let request = AFHTTPRequestOperationManager()
request.requestSerializer.setValue(self.acceptHeader, forHTTPHeaderField: "ACCEPT")
request.requestSerializer.setValue(self.authHeader+authToken, forHTTPHeaderField: "AUTHORIZATION")
request.GET(self.url, parameters: nil, success: { (request: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
// The following 3 lines are required as referenced here: http://stackoverflow.com/a/26741208/535363
var tmp = NSMutableDictionary()
tmp.setObject("temp", forKey: "ExampleApp")
self.logins = tmp
// Get the properties from my server response
let properties: NSDictionary = response.objectForKey("properties") as NSDictionary
let amazonId = properties.objectForKey("amazon_identity") as String
let amazonToken = properties.objectForKey("token") as String
// Set the identityId and token for the ExampleAppIdentityProvider
self.identityId = amazonId
self._token = amazonToken
task.setResult(response)
}, failure: { (request: AFHTTPRequestOperation!, error: NSError!) -> Void in
task.setError(error)
})*/
return task.task
}
}
And
import XCTest
#testable import My
class MyTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock {
// Put the code you want to measure the time of here.
}
}
func testGetUnauthCognitoId() {
let myAuth = MyAuth()
XCTAssertTrue(myAuth.getUnauthCognitoId())
}
}
It turns out that if you create a default service configuration within the application:didFinishLaunchingWithOptions: application delegate method in your app delegate file as described here:
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: AWSRegionType.USEast1, identityPoolId: cognitoIdentityPoolId)
let defaultServiceConfiguration = AWSServiceConfiguration(
region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
The SDK will use an unauthenticated identity whenever you try to use any of the AWS services, and you don't necessarily need to create a cognitoIdentity object.
getIdentityId returns an AWSTask. Since AWSTask is essentially BFTask with a different name, you can get the identityId using the continueWithBlock syntax shown on the BFTask page. Something like:
credentialProvider.getIdentityId().continueWithBlock {
(task: AWSTask!) -> AWSTask in
if task.error() {
// failed to retrieve identityId.
} else {
print("identityId", task.result())
}
Related
im fairly new to swiftUI... pardon the ignorance :) I have most of the code entered in my project from the instructions on the Firebase website. For some reason apple authentication will not successfully authenticate. No idea why, I suspect the nonce part of the code located in 'ConentView' is potentially not being linked to the service. If anyone has any thoughts on why this is occurring I would be greatly appreciative for any help?
Xcode simulator - will not load past this screen
ContentView:
import SwiftUI
import FirebaseAuthUI
import CryptoKit
import AuthenticationServices
struct ContentView: View {
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
String(format: "%02x", $0)
}.joined()
return hashString
}
#ObservedObject private var authStateManager =
FirebaseAuthStateManager()
#State var isShowSheet = false
#State var currentNonce:String?
private func randomNonceString(length: Int = 32) ->
String {
precondition(length > 0)
let charset: [Character] =
***key***
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode =
SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError(
"Unable to generate nonce. SecRandomCopyBytes
failed with OSStatus \(errorCode)"
)
}
return random
}
randoms.forEach { random in
if remainingLength == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
func authorizationController(controller:
ASAuthorizationController, didCompleteWithAuthorization
authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential
as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was
received, but no login request was sent.")
}
guard let appleIDToken =
appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken,
encoding:
.utf8) else {
print("Unable to serialize token string from data: \
(appleIDToken.debugDescription)")
return
}
// Initialize a Firebase credential.
let credential =
OAuthProvider.credential(withProviderID:"apple.com",
idToken: idTokenString,rawNonce: nonce)
// Sign in with Firebase.
Auth.auth().signIn(with: credential) { (authResult,
error) in
if (error != nil) {
// Error. If error.code ==
.MissingOrInvalidNonce, make sure
// you're sending the SHA256-hashed nonce as a
hex string with
// your request to Apple.
print(error?.localizedDescription as Any)
return
}
// User is signed in to Firebase with Apple.
// ...
}
}
func authorizationController(controller:
ASAuthorizationController, didCompleteWithError error:
Error) {
// Handle error.
print("Sign in with Apple errored: \(error)")
}
}
var body: some View {
VStack {
if authStateManager.signInState == false {
// Sign-Out
Button(action: {
self.isShowSheet.toggle()
}) {
Text("Sign-In")
}
} else {
// Sign-In
Button(action: {
do {
try Auth.auth().signOut()
} catch {
print("Error")
}
}) {
Text("Sign-Out")
}
}
}
.sheet(isPresented: $isShowSheet) {
FirebaseUIView(isShowSheet: self.$isShowSheet)
}
}
}
FirebaseUIView:
import SwiftUI
import FirebaseAuthUI
import FirebaseGoogleAuthUI
import FirebaseOAuthUI
import CryptoKit
import AuthenticationServices
import FirebaseEmailAuthUI
struct FirebaseUIView: UIViewControllerRepresentable {
#Binding var isShowSheet: Bool
class Coordinator: NSObject,
FUIAuthDelegate {
// FirebaseUIView
let parent: FirebaseUIView
//
init(_ parent: FirebaseUIView) {
self.parent = parent
}
// MARK: - FUIAuthDelegate
func authUI(_ authUI: FUIAuth, didSignInWith user:
User?, error: Error?) {
// handle user and error as necessary
if let error = error {
//
print("Auth NG:\
(error.localizedDescription)")
}
if let _ = user {
//
}
// Sheet(ModalView)
parent.isShowSheet = false
}
}
func makeCoordinator() -> Coordinator {
// Coordinator
Coordinator(self)
}
func makeUIViewController(context: Context) ->
UINavigationController {
let authUI = FUIAuth.defaultAuthUI()!
// You need to adopt a FUIAuthDelegate protocol to
receive callback
authUI.delegate = context.coordinator
let providers: [FUIAuthProvider] = [
FUIGoogleAuth(authUI: authUI),
FUIOAuth.microsoftAuthProvider(),
// FUIFacebookAuth(authUI: authUI),
// FUIOAuth.twitterAuthProvider(),
FUIEmailAuth(),
// FUIPhoneAuth(authUI:authUI),
FUIOAuth.appleAuthProvider(),
]
authUI.providers = providers
// FirebaseUI
let authViewController = authUI.authViewController()
return authViewController
}
func updateUIViewController(_ uiViewController:
UINavigationController, context: Context) {
}
}
[ContentView[][2]2
Check out SignInWithApple from FirebaseService: https://github.com/rebeloper/FirebaseService
Turns out there was nothing wrong with my code. The moment I switched from simulator to the actual iPhone device the code worked perfectly. This is a known BUG with the simulator in Xcode.
I implemented apple sign in in separate NSObject class and calling from viewController.Whenever I click apple signing button, signin dialog appears with password field.After filling password dialog, signin dialog dismisses and delegate methods not calling.Any idea why delegates not calling.
Note: I added all configurations in Xcode and developer portals.
import AuthenticationServices
class AppleSignin: NSObject {
var successCompletionHandler : ResponseCompletionHandler?
private var authorizationController: ASAuthorizationController?
func signIn(completion: #escaping ResponseCompletionHandler) {
self.vc = vc
self.successCompletionHandler = completion
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = getWindow()
authorizationController.performRequests()
self.authorizationController = authorizationController
}
}
extension AppleSignin: ASAuthorizationControllerDelegate {
// Authorization Failed
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
successCompletionHandler?(.failure(error))
}
// Authorization Succeeded
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
var response = SignInResponse()
response.firstName = appleIDCredential.fullName?.givenName
response.lastName = appleIDCredential.fullName?.familyName
response.emailId = appleIDCredential.email
successCompletionHandler?(.success(response))
} else if let passwordCredential = authorization.credential as? ASPasswordCredential {
// Get user data using an existing iCloud Keychain credential
var response = SignInResponse()
response.firstName = passwordCredential.user
response.emailId = passwordCredential.password
successCompletionHandler?(.success(response))
}
}
}
I'm currently learning Swift and following some tutorials but I'm stuck on a StoreKit issue.
The code works when I provide a single productIdentifier, but when I provide more than 1 in the Set, the entire app hangs on loading. This is in the iOS Simulator, and on a device. I've got 2 identifiers in the set, and both of these work individually, but not at the same time. My code looks the same as the original tutorial (video) so I don't know where I'm going long.
Entire Store.swift file below. Problem appears to be in the fetchProducts function, but I'm not sure. Can anyone point me in the right direction?
import StoreKit
typealias FetchCompletionHandler = (([SKProduct]) -> Void)
typealias PurchaseCompletionHandler = ((SKPaymentTransaction?) -> Void)
class Store: NSObject, ObservableObject {
#Published var allRecipes = [Recipe]() {
didSet {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
for index in self.allRecipes.indices {
self.allRecipes[index].isLocked = !self.completedPurchases.contains(self.allRecipes[index].id)
}
}
}
}
private let allProductIdentifiers = Set(["com.myname.ReceipeStore.test", "com.myname.ReceipeStore.test2"])
private var completedPurchases = [String]()
private var productsRequest: SKProductsRequest?
private var fetchedProducts = [SKProduct]()
private var fetchCompletionHandler: FetchCompletionHandler?
private var purchaseCompletionHandler: PurchaseCompletionHandler?
override init() {
super.init()
startObservingPaymentQueue()
fetchProducts { products in
self.allRecipes = products.map { Recipe(product: $0) }
}
}
private func startObservingPaymentQueue() {
SKPaymentQueue.default().add(self)
}
private func fetchProducts(_ completion: #escaping FetchCompletionHandler) {
guard self.productsRequest == nil else { return }
fetchCompletionHandler = completion
productsRequest = SKProductsRequest(productIdentifiers: allProductIdentifiers)
productsRequest!.delegate = self
productsRequest!.start()
}
private func buy(_ product: SKProduct, competion: #escaping PurchaseCompletionHandler) {
purchaseCompletionHandler = competion
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
}
extension Store {
func product(for identififier: String) -> SKProduct? {
return fetchedProducts.first(where: { $0.productIdentifier == identififier })
}
func purchaseProduct(_ product: SKProduct) {
buy(product) { _ in }
}
}
extension Store: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
var shouldFinishTransactions = false
switch transaction.transactionState {
case .purchased, .restored:
completedPurchases.append(transaction.payment.productIdentifier)
shouldFinishTransactions = true
case .failed:
shouldFinishTransactions = true
case .deferred, .purchasing:
break
#unknown default:
break
}
if shouldFinishTransactions {
SKPaymentQueue.default().finishTransaction(transaction)
DispatchQueue.main.async {
self.purchaseCompletionHandler?(transaction)
self.purchaseCompletionHandler = nil
}
}
}
}
}
// loading products from the store
extension Store: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let loadedProducts = response.products
let invalidProducts = response.invalidProductIdentifiers
guard !loadedProducts.isEmpty else {
print("Could not load the products!")
if !invalidProducts.isEmpty {
print("Invalid products found: \(invalidProducts)")
}
productsRequest = nil
return
}
// cache the feteched products
fetchedProducts = loadedProducts
// notify anyone waiting on the product load (swift UI view)
DispatchQueue.main.async {
self.fetchCompletionHandler?(loadedProducts)
self.fetchCompletionHandler = nil
self.productsRequest = nil
}
}
}```
It looks like you're running all of your requests on the main DispatchQueue, this will block other main queue work until completed. You should consider handling some of these tasks with a custom concurrent queue. This bit of sample code should get the ball rolling.
func requestProducts(_ productIdentifiers: Set<ProductIdentifier>, handler: #escaping ProductRequestHandler) {
// Set request handler
productRequest?.cancel()
productRequestHandler = handler
// Request
productRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productRequest?.delegate = self
productRequest?.start()
}
func requestPrices() {
// Retry interval, 5 seconds, set this to your liking
let retryTimeOut = 5.0
var local1: String? = nil
var local2: String? = nil
let bundleIdentifier = Bundle.main.bundleIdentifier!
let queue = DispatchQueue(label: bundleIdentifier + ".IAPQueue", attributes: .concurrent)
// Request price
queue.async {
var trying = true
while(trying) {
let semaphore = DispatchSemaphore(value: 0)
requestProducts(Set(arrayLiteral: SettingsViewController.pID_1000Credits, SettingsViewController.pID_2000Credits)) { (response, error) in
local1 = response?.products[0].localizedPrice
local2 = response?.products[1].localizedPrice
semaphore.signal()
}
// We will keep checking on this thread until completed
_ = semaphore.wait(timeout: .now() + retryTimeOut)
if(local2 != nil) { trying = false }
}
// Update with main thread once request is completed
DispatchQueue.main.async {
self.price1 = local1 ?? "$0.99"
self.price2 = local2 ?? "$1.99"
}
}
}
extension SKProduct {
// Helper function, not needed for this example
public var localizedPrice: String? {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = self.priceLocale
return formatter.string(from: self.price)
}
I am creating an onboarding portion of an app which gets the user's contacts to check which already have the app to add as friends. I'm using the CNContact framework. I have created several methods I'm using to get a full list of the users' contacts, check if they have the app, and enumerate them in a UITableView. However, when the view loads, the app crashes with the error "A property was not requested when contact was fetched." I already make a fetch request with the keys CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, ad CNContactImageDataKey. I've included all my code here:
import Foundation
import Contacts
import PhoneNumberKit
struct ContactService {
static func createContactArray() -> [CNContact] {
var tempContacts = [CNContact]()
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let _ = error {
print("failed to request access to contacts")
return
}
if granted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
request.sortOrder = CNContactSortOrder.familyName
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, stop) in
tempContacts.append(contact)
})
} catch {
print("unable to fetch contacts")
}
print("created contact list")
}
}
return tempContacts
}
static func createFetchedContactArray(contactArray: [CNContact], completion: #escaping ([FetchedContact]?) -> Void) -> Void {
var temp = [FetchedContact]()
getNumsInFirestore { (nums) in
if let nums = nums {
for c in contactArray {
let f = FetchedContact(cnContact: c, numsInFirebase: nums)
temp.append(f)
}
return completion(temp)
} else {
print("Error retrieving numbers")
}
}
return completion(nil)
}
static func getNumsInFirestore(_ completion: #escaping (_ nums : [String]?) -> Void ) -> Void {
var numsInFirebase = [String]()
let db = FirestoreService.db
db.collection(Constants.Firestore.Collections.users).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting user documents: \(err)")
completion(nil)
} else {
for doc in querySnapshot!.documents {
let dict = doc.data()
numsInFirebase.append(dict[Constants.Firestore.Keys.phone] as! String)
}
completion(numsInFirebase)
}
}
}
static func getPhoneStrings(contact: CNContact) -> [String] {
var temp = [String]()
let cnPhoneNums = contact.phoneNumbers
for n in cnPhoneNums {
temp.append(n.value.stringValue)
}
return temp
}
static func hasBump(contact: CNContact, completion: #escaping (_ h: Bool) -> Void ) -> Void {
let contactNums = ContactService.getPhoneStrings(contact: contact)
ContactService.getNumsInFirestore { (nums) in
if let nums = nums {
return completion(contactNums.contains(where: nums.contains))
} else {
print("Error retrieving numbers from firestore")
return completion(false)
}
}
}
static func anyContactsWithBump(completion: #escaping (_ yes: Bool) -> Void) {
let contacts = createContactArray()
var tempBool = false
let workgroup = DispatchGroup()
for c in contacts {
workgroup.enter()
hasBump(contact: c) { (has) in
if has {
tempBool = true
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
completion(tempBool)
}
}
}
Then I call the methods in the view controller class:
import UIKit
import Contacts
import FirebaseDatabase
import Firebase
import FirebaseFirestore
class AddContactsVC: UIViewController {
var fetchedContactsWithBump: [FetchedContact] = []
override func viewDidLoad() {
let cnContacts = ContactService.createContactArray()
var contactsWithBump = [CNContact]()
let workgroup = DispatchGroup()
for contact in cnContacts {
workgroup.enter()
ContactService.hasBump(contact: contact) { (has) in
if has {
contactsWithBump.append(contact)
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
print("Number of contacts with Bump: \(contactsWithBump.count)")
ContactService.createFetchedContactArray(contactArray: contactsWithBump) { (fetchedContacts) in
if let fetchedContacts = fetchedContacts {
self.fetchedContactsWithBump = fetchedContacts
} else {
print("Error creating fetchedContacts array.")
}
}
}
I also get the message "Error creating fetchedContacts array" in the console, so I know something is going wrong with that method, I'm just not sure what. Any help is appreciated!
Edit: The exception is thrown at 3 points: 1 at the first line of my FetchedContact init method, which looks like this:
init(cnContact: CNContact, numsInFirebase: [String]) {
if cnContact.imageDataAvailable { self.image = UIImage(data: cnContact.imageData!) }
It also points to the line let f = FetchedContact(cnContact: c, numsInFirebase: nums) in createFetched contact array, and finally at my completion(numsInFirebase) call in getNumsInFirestore.
To start with
let contacts = createContactArray()
will always return an empty array.
This function has a return statement outside the closure so will immediately return an empty array.
Change createContactArray to use a completion handler like the other functions you have to populate contacts from inside the closure.
I went by tutorial for creating workout session but these two functions never get called.
This is all in watch part of the application
let healthStore = HKHealthStore()
var configuration: HKWorkoutConfiguration!
var session: HKWorkoutSession!
var builder: HKLiveWorkoutBuilder!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
let typesToShare: Set = [
HKQuantityType.workoutType()
]
// The quantity types to read from the health store.
let typesToRead: Set = [
HKQuantityType.quantityType(forIdentifier: .heartRate)!,
HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,
HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)!
]
// Request authorization for those quantity types.
healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead) { (success, error) in
// Handle errors here.
}
configuration = HKWorkoutConfiguration()
configuration.activityType = .running
configuration.locationType = .outdoor
do {
session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
builder = session.associatedWorkoutBuilder()
} catch {
dismiss()
return
}
builder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
workoutConfiguration: configuration)
session.startActivity(with: Date())
builder.beginCollection(withStart: Date()) { (success, error) in
}
}
func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
for type in collectedTypes {
guard let quantityType = type as? HKQuantityType else {
return // Nothing to do.
}
// Calculate statistics for the type.
let statistics = workoutBuilder.statistics(for: quantityType)
//let label = labelForQuantityType(quantityType)
DispatchQueue.main.async() {
// Update the user interface.
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let value = statistics?.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
print("\(roundedValue) BPM")
}
}
}
func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
let lastEvent = workoutBuilder.workoutEvents.last
DispatchQueue.main.async() {
// Update the user interface here.
print(lastEvent)
}
}
Nothing gets ever printed, and no breakpoint inside those two functions is ever hit.
What am I missing?
It seems like the session is running since there is this small icon of human running when i minimalize the app
You did not connect the delegates.
builder.delegate = self
Your methods will never be called if swift does not know that you are using your interface controller as a delegate for the builder.