Xcode swift SKScene in app purchase - xcode

I'm trying to create in app purchases on an SKScene in my game. The scene is called coinShop. Basically, an IAP that gives the user 300 coins. When I tried copying code, it said SKScene can not conform to the delegates I tried adding. If anybody knows how do in app purchases with a scene instead of a viewcontroller, I would love to know. Here is all the code relating to in app purchases
in the didMoveToView(), i have
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
requestProductInfo()
productIDs.append("com.ClearRockTechnologies.alien-anarchy.300Coins")
and then i have the following functions
func requestProductInfo() {
if SKPaymentQueue.canMakePayments() {
let productIdentifiers = NSSet(array: productIDs)
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as Set<NSObject> )
productRequest.delegate = self
productRequest.start()
}
else {
print("Cannot perform In App Purchases.")
}
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
if response.products.count != 0 {
for product in response.products {
productsArray.append(product)
}
}
else {
print("There are no products.")
}
if response.invalidProductIdentifiers.count != 0 {
print(response.invalidProductIdentifiers.description)
}
}
func showActions() {
let payment = SKPayment(product: self.productsArray[0] as SKProduct)
SKPaymentQueue.defaultQueue().addPayment(payment)
//self.transactionInProgress = true
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) {
for transaction in transactions as! [SKPaymentTransaction] {
switch transaction.transactionState {
case SKPaymentTransactionState.Purchased:
print("Transaction completed successfully.")
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
coins+=300
NSUserDefaults.standardUserDefaults().setInteger(coins, forKey: "coins")
case SKPaymentTransactionState.Failed:
print("Transaction Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
default:
print(transaction.transactionState.rawValue)
}
}
}
and here are my two errors,
Type 'coinShop' does not conform to protocol 'SKPaymentTransactionObserver'
'NSSet' is not implicitly convertible to 'Set'; did you mean to use 'as' to explicitly convert?
Error 1 has no suggestion, and error 2 tells me to add as "Set< NSObject >" to this line
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as Set<NSObject> )
but I clearly already have that. Any help would be appreciated.

Related

SwiftUI: How to drag and drop an email from Mail on macOS

As a follow up on #Asperi's answer of my question on how to drag and drop contacts and, I'd also like to be able to drag and drop email in the same way. Here is my code:
import SwiftUI
import UniformTypeIdentifiers
let uttypes = [String(kUTTypeEmailMessage)]
struct ContentView: View
{
let dropDelegate = EmailDropDelegate()
var body: some View
{
VStack
{
Text("Drag your email here!")
.padding(20)
}
.onDrop(of: uttypes, delegate: dropDelegate)
}
}
struct EmailDropDelegate: DropDelegate
{
func validateDrop(info: DropInfo) -> Bool
{
return true
}
func dropEntered(info: DropInfo)
{
print ("Drop Entered")
}
func performDrop(info: DropInfo) -> Bool
{
let items = info.itemProviders(for: uttypes)
for item in items
{
print (item.registeredTypeIdentifiers) // prints []
item.loadDataRepresentation(forTypeIdentifier: kUTTypeEmailMessage as String, completionHandler: { (data, error) in
if let data = data
{
print(data)
}
})
}
return true
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm not getting any data back that I can decode.
2020-11-08 09:34:54.877532+0000 DropContact[3856:124769] Cannot find representation conforming to type public.email-message
This feature has been eluding me forever so any help would be very much appreciated.
Well... the approach is the same, the only thing is that Apple Mail does not provide kUTTypeEmailMessage UTI representation on drag (copy)
If we register self for generic kUTTypeContent UTI and investigate content of pasteboard on drop mail from Mail, we get:
Ie, here is a complete list of representations:
com.apple.mail.PasteboardTypeMessageTransfer,
com.apple.mail.PasteboardTypeAutomator,
com.apple.pasteboard.promised-file-url,
dyn.ah62d4rv4gu8y6y4usm1044pxqzb085xyqz1hk64uqm10c6xenv61a3k,
NSPromiseContentsPboardType,
com.apple.pasteboard.promised-file-content-type,
dyn.ah62d4rv4gu8yc6durvwwa3xmrvw1gkdusm1044pxqyuha2pxsvw0e55bsmwca7d3sbwu,
Apple files promise pasteboard type,
public.url,
CorePasteboardFlavorType 0x75726C20,
dyn.ah62d4rv4gu8yc6durvwwaznwmuuha2pxsvw0e55bsmwca7d3sbwu,
Apple URL pasteboard type,
public.url-name,
CorePasteboardFlavorType 0x75726C6E,
public.utf8-plain-text,
NSStringPboardType
so now you can load data of any of those types from above (except of course Apple's own privates). And, by the way, that list might (and rather will) depend on macOS version.
I have no solution for this but might be on a path to it.
As I mentioned in a comment, it looks to me that SwiftUI does not have a way to fulfil file promises yet.
Afaik Mail, Photos and Safari using file promises while dragging images or mails.
This might help some one else with a solution.
func performDrop(info: DropInfo) -> Bool {
let pasteboard = NSPasteboard(name: .drag)
guard let items = pasteboard.pasteboardItems else { return false }
// it returns the content of the public.url → is an encoded message url
print(pasteboard.readObjects(forClasses: [NSURL.self]))
// getting available types for pasteboard
let types = NSFilePromiseReceiver.readableDraggedTypes.map { NSPasteboard.PasteboardType($0) }
for type in types {
for item in items {
print("type:", type)
print(item.data(forType: type))
}
}
}
This prints this:
Optional([message:%3Cf6304df5.BAAAA6Ge1-UAAAAAAAAAALTyTrMAAVNI2qYAAAAAAAcXzQBjqtkK#mailjet.com%3E])
type: NSPasteboardType(_rawValue: com.apple.NSFilePromiseItemMetaData)
nil
type: NSPasteboardType(_rawValue: dyn.ah62d4rv4gu8yc6durvwwa3xmrvw1gkdusm1044pxqyuha2pxsvw0e55bsmwca7d3sbwu)
nil
type: NSPasteboardType(_rawValue: com.apple.pasteboard.promised-file-content-type)
Optional(20 bytes)
But I don't know what data type com.apple.pasteboard.promised-file-content-type is … Maybe some kind of NSFilePromiseReceiver.
Maybe this helps someone.
Edit
Tried something else, maybe something more promising.
func performDrop(info: DropInfo) -> Bool {
let pasteboard = NSPasteboard(name: .drag)
guard let filePromises = pasteboard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) else { return false }
guard let receiver = filePromises.first as? NSFilePromiseReceiver else { return false }
let queue = OperationQueue.main
receiver.receivePromisedFiles(atDestination: URL.temporaryDirectory, operationQueue: queue) { (url, error) in
print(url, error)
}
}
In this example I'm able to get the NSFilePromiseReceiver but something is off.
After around 20 to 30s the callback is called and I get the URL finally – so there is maybe some potential to improve. I think it has something todo with the Queue.
Edit
I was able to move/copy the dropped mail into the download folder.
func performDrop(info: DropInfo) -> Bool {
let pasteboard = NSPasteboard(name: .drag)
guard let filePromises = pasteboard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) else { return false }
guard let receiver = filePromises.first as? NSFilePromiseReceiver else { return false }
let dispatchGroup = DispatchGroup()
let queue = OperationQueue()
let destUrl = URL.downloadsDirectory
dispatchGroup.enter()
var urls: [URL] = []
receiver.receivePromisedFiles(atDestination: destUrl, operationQueue: queue) { (url, error) in
if let error = error {
print(error)
} else {
urls.append(url)
}
print(receiver.fileNames, receiver.fileTypes)
dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main, execute: {
print(urls)
})
}

How to display Game Center leaderboard with SwiftUI

I created a tester app to test adding a GameCenter leaderboard to a simple SwiftUI game I am creating. I have been unable to figure out how to display the Game Center leaderboard with all the scores.
I have created a class containing all the Game Center functions (authentication and adding score to the leaderboard. This is called from the main ContentView view. I can't figure out how to make it show the leaderboard (or even the gamecenter login screen if the player isn't already logged in.)
This is my GameCenterManager class:
class GameCenterManager {
var gcEnabled = Bool() // Check if the user has Game Center enabled
var gcDefaultLeaderBoard = String() // Check the default leaderboardID
var score = 0
let LEADERBOARD_ID = "grp.colorMatcherLeaderBoard_1" //Leaderboard ID from Itunes Connect
// MARK: - AUTHENTICATE LOCAL PLAYER
func authenticateLocalPlayer() {
let localPlayer: GKLocalPlayer = GKLocalPlayer.local
localPlayer.authenticateHandler = {(ViewController, error) -> Void in
if((ViewController) != nil) {
print("User is not logged into game center")
} else if (localPlayer.isAuthenticated) {
// 2. Player is already authenticated & logged in, load game center
self.gcEnabled = true
// Get the default leaderboard ID
localPlayer.loadDefaultLeaderboardIdentifier(completionHandler: { (leaderboardIdentifer, error) in
if error != nil { print(error ?? "error1")
} else { self.gcDefaultLeaderBoard = leaderboardIdentifer! }
})
print("Adding GameCenter user was a success")
} else {
// 3. Game center is not enabled on the users device
self.gcEnabled = false
print("Local player could not be authenticated!")
print(error ?? "error2")
}
}
} //authenticateLocalPlayer()
func submitScoreToGC(_ score: Int){
let bestScoreInt = GKScore(leaderboardIdentifier: LEADERBOARD_ID)
bestScoreInt.value = Int64(score)
GKScore.report([bestScoreInt]) { (error) in
if error != nil {
print(error!.localizedDescription)
} else {
print("Best Score submitted to your Leaderboard!")
}
}
}//submitScoreToGc()
}
and here is the ContentView struct:
struct ContentView: View {
//GameCenter
init() {
self.gameCenter = GameCenterManager()
self.gameCenter.authenticateLocalPlayer()
}
#State var score = 0
var gcEnabled = Bool() //Checks if the user had enabled GameCenter
var gcDefaultLeaderboard = String() //Checks the default leaderboard ID
let gameCenter: GameCenterManager
/*End GameCenter Variables */
var body: some View {
HStack {
Text("Hello, world!")
Button(action: {
self.score += 1
print("Score increased by 10. It is now \(self.score)")
self.gameCenter.submitScoreToGC(self.score)
}) {
Text("Increase Score")
}
}
}
}
Would greatly appreciate any help in fixing the problem.
I have a fix.
I use Game Center successfully in my SwiftUI App Sound Matcher. Code snippets to follow.
The code doesn't exactly follow the SwiftUI declarative philosophy but it works perfectly. I added snippets to SceneDelegate and ContentView plus used I used a GameKitHelper class similar to the one Thomas created for his test app. I based my version on code I found on raywenderlich.com.
I actually tried using a struct conforming to UIViewControllerRepresentable as my first attempt, following the same line of thought as bg2b, however it kept complaining that the game centre view controller needed to be presented modally. Eventually I gave up and tried my current more successful approach.
For SwiftUI 1.0 and iOS 13
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let contentView = ContentView()
.environmentObject(GameKitHelper.sharedInstance) // publish enabled state
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
// new code to create listeners for the messages
// you will be sending later
PopupControllerMessage.PresentAuthentication
.addHandlerForNotification(
self,
handler: #selector(SceneDelegate
.showAuthenticationViewController))
PopupControllerMessage.GameCenter
.addHandlerForNotification(
self,
handler: #selector(SceneDelegate
.showGameCenterViewController))
// now we are back to the standard template
// generated when your project was created
self.window = window
window.makeKeyAndVisible()
}
}
// pop's up the leaderboard and achievement screen
#objc func showGameCenterViewController() {
if let gameCenterViewController =
GameKitHelper.sharedInstance.gameCenterViewController {
self.window?.rootViewController?.present(
gameCenterViewController,
animated: true,
completion: nil)
}
}
// pop's up the authentication screen
#objc func showAuthenticationViewController() {
if let authenticationViewController =
GameKitHelper.sharedInstance.authenticationViewController {
self.window?.rootViewController?.present(
authenticationViewController, animated: true)
{ GameKitHelper.sharedInstance.enabled =
GameKitHelper.sharedInstance.gameCenterEnabled }
}
}
}
// content you want your app to display goes here
struct ContentView: View {
#EnvironmentObject var gameCenter : GameKitHelper
#State private var isShowingGameCenter = false { didSet {
PopupControllerMessage
.GameCenter
.postNotification() }}
var body: some View {
VStack {
if self.gameCenter.enabled
{
Button(action:{ self.isShowingGameCenter.toggle()})
{ Text(
"Press to show leaderboards and achievements")}
}
// The authentication popup will appear when you first enter
// the view
}.onAppear() {GameKitHelper.sharedInstance
.authenticateLocalPlayer()}
}
}
import GameKit
import UIKit
// Messages sent using the Notification Center to trigger
// Game Center's Popup screen
public enum PopupControllerMessage : String
{
case PresentAuthentication = "PresentAuthenticationViewController"
case GameCenter = "GameCenterViewController"
}
extension PopupControllerMessage
{
public func postNotification() {
NotificationCenter.default.post(
name: Notification.Name(rawValue: self.rawValue),
object: self)
}
public func addHandlerForNotification(_ observer: Any,
handler: Selector) {
NotificationCenter.default .
addObserver(observer, selector: handler, name:
NSNotification.Name(rawValue: self.rawValue), object: nil)
}
}
// based on code from raywenderlich.com
// helper class to make interacting with the Game Center easier
open class GameKitHelper: NSObject, ObservableObject, GKGameCenterControllerDelegate {
public var authenticationViewController: UIViewController?
public var lastError: Error?
private static let _singleton = GameKitHelper()
public class var sharedInstance: GameKitHelper {
return GameKitHelper._singleton
}
private override init() {
super.init()
}
#Published public var enabled :Bool = false
public var gameCenterEnabled : Bool {
return GKLocalPlayer.local.isAuthenticated }
public func authenticateLocalPlayer () {
let localPlayer = GKLocalPlayer.local
localPlayer.authenticateHandler = {(viewController, error) in
self.lastError = error as NSError?
self.enabled = GKLocalPlayer.local.isAuthenticated
if viewController != nil {
self.authenticationViewController = viewController
PopupControllerMessage
.PresentAuthentication
.postNotification()
}
}
}
public var gameCenterViewController : GKGameCenterViewController? { get {
guard gameCenterEnabled else {
print("Local player is not authenticated")
return nil }
let gameCenterViewController = GKGameCenterViewController()
gameCenterViewController.gameCenterDelegate = self
gameCenterViewController.viewState = .achievements
return gameCenterViewController
}}
open func gameCenterViewControllerDidFinish(_
gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismiss(
animated: true, completion: nil)
}
}
Update: For SwiftUI 2.0 and iOS 14 the code is lot easier
import GameKit
enum Authenticate
{
static func user() {
let localPlayer = GKLocalPlayer.local
localPlayer.authenticateHandler = { _, error in
guard error == nil else {
print(error?.localizedDescription ?? "")
return
}
GKAccessPoint.shared.location = .topLeading
GKAccessPoint.shared.isActive =
localPlayer.isAuthenticated
}
}
}
import SwiftUI
// content you want your app to display goes here
struct ContentView: View {
var body: some View {
Text( "Start Game")
// The authentication popup will appear when you first enter
// the view
}.onAppear() { Authenticate.user()}
}
}
EDIT 2023: as mentioned in comments, GKScore is now deprecated. I don't have an updated solution to present.
Partial answer for you here. I'm able to download leaderboard scores and display them in a SwiftUI list provided the device (or simulator) is logged into iCloud and has GameCenter already enabled in settings. I have not attempted to make a gameCenter authentication view controller appear if that is not the case.
Thank you for the code in your question. I used your GameCenterManager() but put it in my AppDelegate:
let gameCenter = GameCenterManager()
Below is my ShowRankings.swift SwiftUI View. I'm able to successfully authenticate and get the scores. But I still have "anomalies". The first time I run this (in simulator) I get the expected "User is not logged into Game Center" error indicating the ViewController in your GameCenterManager is not nil (I never even attempt to display it). But then I'm able to successfully get the scores and display them in a list.
import SwiftUI
import GameKit
struct ShowRankings: View {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let leaderBoard = GKLeaderboard()
#State var scores: [GKScore] = []
var body: some View {
VStack {
Button(action: {
self.updateLeader()
}) {
Text("Refresh leaderboard")
}
List(scores, id: \.self) { score in
Text("\(score.player.alias) \(score.value)")
}
}.onAppear() {
self.appDelegate.gameCenter.authenticateLocalPlayer()
self.updateLeader()
}
}
func updateLeader() {
let leaderBoard: GKLeaderboard = GKLeaderboard()
leaderBoard.identifier = "YOUR_LEADERBOARD_ID_HERE"
leaderBoard.timeScope = .allTime
leaderBoard.loadScores { (scores, error) in
if let error = error {
debugPrint("leaderboard loadScores error \(error)")
} else {
guard let scores = scores else { return }
self.scores = scores
}
}
}
}
An alternative solution is to create a UIViewControllerRepresentable for GameCenter which takes a leaderboard ID to open. This makes it simple to open a specific leader board.
public struct GameCenterView: UIViewControllerRepresentable {
let viewController: GKGameCenterViewController
public init(leaderboardID : String?) {
if leaderboardID != nil {
self.viewController = GKGameCenterViewController(leaderboardID: leaderboardID!, playerScope: GKLeaderboard.PlayerScope.global, timeScope: GKLeaderboard.TimeScope.allTime)
}
else{
self.viewController = GKGameCenterViewController(state: GKGameCenterViewControllerState.leaderboards)
}
}
public func makeUIViewController(context: Context) -> GKGameCenterViewController {
let gkVC = viewController
gkVC.gameCenterDelegate = context.coordinator
return gkVC
}
public func updateUIViewController(_ uiViewController: GKGameCenterViewController, context: Context) {
return
}
public func makeCoordinator() -> GKCoordinator {
return GKCoordinator(self)
}
}
public class GKCoordinator: NSObject, GKGameCenterControllerDelegate {
var view: GameCenterView
init(_ gkView: GameCenterView) {
self.view = gkView
}
public func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismiss(animated: true, completion: nil)
}
}
To use just add the below wherever it is needed to display a leaderboard.
GameCenterView(leaderboardID: "leaderBoardID")

Receive promised e-mail in macOS 10.12+

Previously, I was using the following to discover e-mail meta-data from a drag & dropped e-mail(/-thread) from Mail.app.
if let filenames = draggingInfo.namesOfPromisedFilesDropped(atDestination: URL(fileURLWithPath: destinationDir!)) {
/// TODO: in future implementation Mail might return multiple filenames here.
/// So we will keep this structure to iterate the filenames
//var aPaths: [String] = []
//for _ in filenames {
if let aPath = pb.string(forType: "com.apple.pasteboard.promised-file-url") {
return aPath
}
//}
//return aPaths
}
Kind of janky, but it worked, since "com.apple.pasteboard.promised-file-url" was only supplied in those situations.
Since 10.12 however, the API seems to have changed, and looking at the WWDC2016 talk it appears that Apple wants us to use NSFilePromiseReceiver now.
I've tried a couple of approaches but I can't get a promised file URL to pop out.
Setup:
class DropzoneView: NSView {
var supportedDragTypes = [
kUTTypeURL as String, // For any URL'able types
"public.url-name", // E-mail title
"public.utf8-plain-text", // Plaintext item / E-mail thread title / calendar event date placeholder
"com.apple.pasteboard.promised-file-content-type", // Calendar event / Web URL / E-mail thread type detection
"com.apple.mail.PasteboardTypeMessageTransfer", // E-mail thread detection
"NSPromiseContentsPboardType", // E-mail thread meta-data
"com.apple.pasteboard.promised-file-url", // E-mail thread meta-data
"com.apple.NSFilePromiseItemMetaData" // E-mail thread meta-data
]
override func viewDidMoveToSuperview() {
var dragTypes = self.supportedDragTypes.map { (type) -> NSPasteboard.PasteboardType in
return NSPasteboard.PasteboardType(type)
} // Experiment:
dragTypes.append(NSPasteboard.PasteboardType.fileContentsType(forPathExtension: "eml"))
dragTypes.append(NSPasteboard.PasteboardType.fileContentsType(forPathExtension: "emlx"))
self.registerForDraggedTypes(dragTypes)
}
}
Handling:
extension DropzoneView {
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
return .copy
}
override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
return .copy
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
let pasteboard: NSPasteboard = sender.draggingPasteboard()
guard let filePromises = pasteboard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) as? [NSFilePromiseReceiver] else {
return false
}
var files = [Any]()
var errors = [Error]()
let filePromiseGroup = DispatchGroup()
let operationQueue = OperationQueue()
let newTempDirectoryURL = URL(fileURLWithPath: (NSTemporaryDirectory() + (UUID().uuidString) + "/"), isDirectory: true)
do {
try FileManager.default.createDirectory(at: newTempDirectoryURL, withIntermediateDirectories: true, attributes: nil)
}
catch {
return false
}
// Async attempt, either times out after a minute or so (Error Domain=NSURLErrorDomain Code=-1001 "(null)") or gives 'operation cancelled' error
filePromises.forEach({ filePromiseReceiver in
filePromiseGroup.enter()
filePromiseReceiver.receivePromisedFiles(atDestination: newTempDirectoryURL,
options: [:],
operationQueue: operationQueue,
reader: { (url, error) in
Swift.print(url)
if let error = error {
errors.append(error)
}
else if url.isFileURL {
files.append(url)
}
else {
Swift.print("No loadable URLs found")
}
filePromiseGroup.leave()
})
})
filePromiseGroup.notify(queue: DispatchQueue.main,
execute: {
// All done, check your files and errors array
Swift.print("URLs: \(files)")
Swift.print("errors: \(errors)")
})
Swift.print("URLs: \(files)")
return true
}
Other attempts:
// returns nothing
if let filenames = pasteboard.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "com.apple.pasteboard.promised-file-url")) as? NSArray {
Swift.print(filenames)
}
// doesn't result in usable URLs either
if let urls = pasteboard.readObjects(forClasses: [NSPasteboardItem.self /*NSURL.self, ???*/], options: [:]) as? [...
Any pointers would be greatly appreciated.
I have managed to get the file to "pop out" but I cannot get the details for them. It transfers immediately and then hangs for 60 seconds before returning an error message.
Maybe it's a clue but the checkExtension method never returns unless commented out and set to true.
Hopefully this helps kick the can down the road a bit:
class DropView: NSView
{
var filePath: String?
required init?(coder: NSCoder) {
super.init(coder: coder)
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.red.cgColor
registerForDraggedTypes([NSPasteboard.PasteboardType
.fileNameType(forPathExtension: ".eml"), NSPasteboard.PasteboardType.filePromise])
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
if checkExtension(sender) == true
{
self.layer?.backgroundColor = NSColor.blue.cgColor
return .copy
}
else
{
return NSDragOperation()
}
}
fileprivate func checkExtension(_ drag: NSDraggingInfo) -> Bool
{
return true
// guard let board = drag.draggingPasteboard().propertyList(forType: NSPasteboard.PasteboardType(rawValue: "com.apple.mail.PasteboardTypeMessageTransfer")) as? NSArray,
// let path = board[0] as? String
// else
// {
// return false
// }
//
// let suffix = URL(fileURLWithPath: path).pathExtension
// for ext in self.expectedExt
// {
// if ext.lowercased() == suffix
// {
// return true
// }
// }
// return false
}
override func draggingExited(_ sender: NSDraggingInfo?)
{
self.layer?.backgroundColor = NSColor.gray.cgColor
}
override func draggingEnded(_ sender: NSDraggingInfo)
{
self.layer?.backgroundColor = NSColor.gray.cgColor
}
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool
{
let pasteboard: NSPasteboard = sender.draggingPasteboard()
guard let filePromises = pasteboard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) as? [NSFilePromiseReceiver] else {
return false
}
print ("Files dropped")
var files = [URL]()
let filePromiseGroup = DispatchGroup()
let operationQueue = OperationQueue()
let destURL = URL(fileURLWithPath: "/Users/andrew/Temporary", isDirectory: true)
print ("Destination URL: \(destURL)")
filePromises.forEach ({ filePromiseReceiver in
print (filePromiseReceiver)
filePromiseGroup.enter()
filePromiseReceiver.receivePromisedFiles(atDestination: destURL,
options: [:],
operationQueue: operationQueue,
reader:
{ (url, error) in
print ("Received URL: \(url)")
if let error = error
{
print ("Error: \(error)")
}
else
{
files.append(url)
}
print (filePromiseReceiver.fileNames, filePromiseReceiver.fileTypes)
filePromiseGroup.leave()
})
})
filePromiseGroup.notify(queue: DispatchQueue.main,
execute:
{
print ("Files: \(files)")
print ("Done")
})
return true
}
}
The output of this is a bit weird. The url variable aways repeats the name of the directory that I passed in eg
Files dropped
Destination URL: file:///Users/andrew/Temporary/
<NSFilePromiseReceiver: 0x6000000a1aa0>
** one minute gap **
Received URL: file:///Users/andrew/Temporary/Temporary/
Error: Error Domain=NSURLErrorDomain Code=-1001 "(null)"
["Temporary"] ["com.apple.mail.email"]
Files: []
Done
I saw this error when trying to receive promised files to an invalid destination url.
In my case I was using Ole Begemann's Temporary File Helper and accidentally letting it go out of scope, which deleted the directory before anything could be copied.
receivePromisedFiles gave me the -1001 timeout error after a long wait, but it did still pass a URL that would have been correct given my inputs. Obviously no file was at that location.
When I changed to a valid url all worked as expected. It might be worth checking Sandbox issues etc.
Apple now have some useful example projects in the File Promises section here:
https://developer.apple.com/documentation/appkit/documents_data_and_pasteboard

Heart rate monitor only app for watchOS 2 records extra calories and shows exercise

Basically I am working on a sleep monitoring application that monitors heart rate as well. So, I don't want to start any workout activity but I think that's the way apple works!
Here's the heart rate only code I am using:
#IBOutlet private weak var label: WKInterfaceLabel!
#IBOutlet private weak var deviceLabel : WKInterfaceLabel!
#IBOutlet private weak var heart: WKInterfaceImage!
#IBOutlet private weak var startStopButton : WKInterfaceButton!
let healthStore = HKHealthStore()
//State of the app - is the workout activated
var workoutActive = false
// define the activity type and location
var workoutSession : HKWorkoutSession?
let heartRateUnit = HKUnit(fromString: "count/min")
var anchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
override func willActivate() {
super.willActivate()
guard HKHealthStore.isHealthDataAvailable() == true else {
label.setText("not available")
return
}
guard let quantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else {
displayNotAllowed()
return
}
let dataTypes = Set(arrayLiteral: quantityType)
healthStore.requestAuthorizationToShareTypes(nil, readTypes: dataTypes) { (success, error) -> Void in
if success == false {
self.displayNotAllowed()
}
}
}
func displayNotAllowed() {
label.setText("not allowed")
}
func workoutSession(workoutSession: HKWorkoutSession, didChangeToState toState: HKWorkoutSessionState, fromState: HKWorkoutSessionState, date: NSDate) {
switch toState {
case .Running:
workoutDidStart(date)
case .Ended:
workoutDidEnd(date)
default:
print("Unexpected state \(toState)")
}
}
func workoutSession(workoutSession: HKWorkoutSession, didFailWithError error: NSError) {
// Do nothing for now
NSLog("Workout error: \(error.userInfo)")
}
func workoutDidStart(date : NSDate) {
if let query = createHeartRateStreamingQuery(date) {
healthStore.executeQuery(query)
} else {
label.setText("cannot start")
}
}
func workoutDidEnd(date : NSDate) {
if let query = createHeartRateStreamingQuery(date) {
healthStore.stopQuery(query)
label.setText("---")
} else {
label.setText("cannot stop")
}
}
// MARK: - Actions
#IBAction func startBtnTapped() {
if (self.workoutActive) {
//finish the current workout
self.workoutActive = false
self.startStopButton.setTitle("Start")
if let workout = self.workoutSession {
healthStore.endWorkoutSession(workout)
}
} else {
//start a new workout
self.workoutActive = true
self.startStopButton.setTitle("Stop")
startWorkout()
}
}
func startWorkout() {
self.workoutSession = HKWorkoutSession(activityType: HKWorkoutActivityType.CrossTraining, locationType: HKWorkoutSessionLocationType.Indoor)
self.workoutSession?.delegate = self
healthStore.startWorkoutSession(self.workoutSession!)
}
func createHeartRateStreamingQuery(workoutStartDate: NSDate) -> HKQuery? {
// adding predicate will not work
// let predicate = HKQuery.predicateForSamplesWithStartDate(workoutStartDate, endDate: nil, options: HKQueryOptions.None)
guard let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else { return nil }
let heartRateQuery = HKAnchoredObjectQuery(type: quantityType, predicate: nil, anchor: anchor, limit: Int(HKObjectQueryNoLimit)) { (query, sampleObjects, deletedObjects, newAnchor, error) -> Void in
guard let newAnchor = newAnchor else {return}
self.anchor = newAnchor
self.updateHeartRate(sampleObjects)
}
heartRateQuery.updateHandler = {(query, samples, deleteObjects, newAnchor, error) -> Void in
self.anchor = newAnchor!
self.updateHeartRate(samples)
}
return heartRateQuery
}
func updateHeartRate(samples: [HKSample]?) {
guard let heartRateSamples = samples as? [HKQuantitySample] else {return}
dispatch_async(dispatch_get_main_queue()) {
guard let sample = heartRateSamples.first else{return}
let value = sample.quantity.doubleValueForUnit(self.heartRateUnit)
self.label.setText(String(UInt16(value)))
// retrieve source from sample
let name = sample.sourceRevision.source.name
self.updateDeviceName(name)
self.animateHeart()
}
}
func updateDeviceName(deviceName: String) {
deviceLabel.setText(deviceName)
}
func animateHeart() {
self.animateWithDuration(0.5) {
self.heart.setWidth(60)
self.heart.setHeight(90)
}
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * double_t(NSEC_PER_SEC)))
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_after(when, queue) {
dispatch_async(dispatch_get_main_queue(), {
self.animateWithDuration(0.5, animations: {
self.heart.setWidth(50)
self.heart.setHeight(80)
})
})
}
} }
To summarize, the unexpected observations are:
1. The time I monitor the heart rate contributes to the green ring in the activity app.
2. Unexpected high amount of calories are being recorded i.e. when the person is on bed or asleep!
Can you please help with the correct code that helps me to monitor and display a person's heart beat at regular interval during his sleep without contributing to the green ring or contributing extra cals. ?
Thanks a lot in advance!
Starting a workout and running the heart rate monitor will drain the apple watch's battery after about 6 hours (if it has a full charge), so having it run continuously while sleeping is probably not realistic at this time.
From what I can tell, starting a workout using workoutSession does 2 things for your app. It keeps your app in the foreground, and it starts taking heart rate sample every few seconds. Have you considered not starting it? Your health kit queries will still work as is and the heart rate monitor still records the users heart rate every 15 minutes or so. The main thing you loose is keeping your app in the foreground, and I am wondering if you need to do that (since the user will be asleep).
To retrieve the last heart rate sample from healthkit:
func getLatestHeartRate() {
let quantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)!
let sortDescriptor = NSSortDescriptor(key:HKSampleSortIdentifierStartDate, ascending: false)
let sampleQuery = HKSampleQuery(sampleType: quantityType, predicate: nil, limit: 1, sortDescriptors: [sortDescriptor])
{ (sampleQuery, results, error ) -> Void in
}
self.healthStore.executeQuery(sampleQuery)
}

delay after requestAuthorizationToShareTypes

I am setting up an iOS 8 app to request Heath Kit Store authorization to share types. The request Read/Write screen shows fine and on selecting Done, I see the completion callback immediately after. In this callback, I am pushing a new view controller. I set a breakpoint for the code that is programmatically pushing the next view controller and this is called immediately, but the transition doesn't occur until about 10 seconds later.
Some code:
#IBAction func enable(sender: AnyObject) {
let hkManager = HealthKitManager()
hkManager.setupHealthStoreIfPossible { (success, error) -> Void in
if let error = error {
println("error = \(error)")
} else {
println("enable HK success = \(success)")
self.nextStep()
}
}
}
func nextStep() {
self.nav!.pushViewController(nextController, animated: true)
}
class HealthKitManager: NSObject {
let healthStore: HKHealthStore!
override init() {
super.init()
healthStore = HKHealthStore()
}
class func isHealthKitAvailable() -> Bool {
return HKHealthStore.isHealthDataAvailable()
}
func setupHealthStoreIfPossible(completion: ((Bool, NSError!) -> Void)!) {
if HealthKitManager.isHealthKitAvailable()
{
healthStore.requestAuthorizationToShareTypes(dataTypesToWrite(), readTypes: dataTypesToRead(), completion: { (success, error) -> Void in
completion(success, error)
})
}
}
func dataTypesToWrite() -> NSSet {
let runningType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let stepType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
return NSSet(objects: runningType, stepType)
}
func dataTypesToRead() -> NSSet {
let runningType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let stepType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let climbedType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierFlightsClimbed)
return NSSet(objects: runningType, stepType, climbedType)
}
}
Any thoughts on what is causing the time delay for the transition?
The problem was that the completion block is returned in the background queue. I just put the transition call back onto the main queue as follows:
hkManager.setupHealthStoreIfPossible { (success, error) -> Void in
if let error = error {
println("error = \(error)")
} else {
dispatch_async(dispatch_get_main_queue(), {
println("enable HK success = \(success)")
self.nextStep()
});
}
}
}

Resources