I am trying to implement Game Center into my app and the code below is reporting the score.
I am trying to accomplish a solution were if the player is not authenticated it should save the score in scoreDictionary via the scoreReporter block. However, when the player is not authenticated it never hits the "If (error != nil)" statement.
As a matter of fact it passes the whole block. If the local player is authenticated it execute the block.
This is the first time i look into both Game Center and blocks in detail so i am a bit lost here.
What i want to accomplish is as described above.
I use 5.1 as target.
-(void)reportScore:(int64_t)score forCategory:(NSString*)category {
NSLog(#"reportScore: %lli", score);
NSLog(#"category: %#",category);
GKScore *scoreReporter = [[GKScore alloc] initWithCategory:category];
scoreReporter.value = score;
NSLog(#"scoreReporter: %#", scoreReporter);
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
NSLog(#"Execute the scoreReporter the block");
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *scoreFilePath = [NSString stringWithFormat:#"%#/scores.plist",[paths
objectAtIndex:0]];
NSMutableDictionary *scoreDictionary=[NSMutableDictionary
dictionaryWithContentsOfFile:scoreFilePath];
if (error != nil)
{
//There was an error so we need to save the score locally and resubmit later
NSLog(#"Saving score for later");
[scoreDictionary setValue:scoreReporter forKey:[NSDate date]];
[scoreDictionary writeToFile:scoreFilePath atomically:YES];
}
}];
}
NSLog output when the player is not authenticated:
reportScore: 80
category: com.xxxxxx.yyyyyyyHighScore
scoreReporter: <GKScore: 0xab611b0>player:(null) rank:0 date:2012-12-19 11:18:04 +0000 value:80 formattedValue:(null) context:0x0
NSLog output when the player is authenticated:
reportScore: 60
category: com.xxxxxx.yyyyyyyHighScore
scoreReporter: <GKScore: 0x964ce30>player:G:280817155 rank:0 date:2012-12-19 11:27:45 +0000 value:60 formattedValue:(null) context:0x0
Execute the scoreReporter the block
The Game Center Programming Guide says "Most Game Center classes function only if there is an authenticated player, and those classes implicitly reference the local player. For example, when your game reports scores to a leaderboard, it always reports scores for the local player. Your game must disable all Game Center features when there is not an authenticated player."
Probably what is going on is that reportScoreWithCompletionHandler: doesn't run at all, so it doesn't "complete", so it doesn't call the completionHandler. Test for an authenticated player before reportScoreWithCompletionHandler: GameKit code typically has several occurrences of the following if/else:
if ( [GKLocalPlayer localPlayer].authenticated ) {
...
} else {
...
}
Related
In iOS 9.3 Apple release new APIs. Now developers can see if a user is currently a member of Apple Music. I'm trying to understand how it works.
My code -only first time- asks the user whether to access the music library but I don't understand how to determine in what ways you can detect if user is a member and open Apple Music to join it. These actions in Shazam works really great. How can I do something like that?
Thanks in advance!
[SKCloudServiceController requestAuthorization:^(SKCloudServiceAuthorizationStatus status) {
NSLog(#"status is %ld", (long)status);
SKCloudServiceController *cloudServiceController = [[SKCloudServiceController alloc] init];
[cloudServiceController requestCapabilitiesWithCompletionHandler:^(SKCloudServiceCapability capabilities, NSError * _Nullable error) {
NSLog(#"%lu %#", (unsigned long)capabilities, error);
if (capabilities >= SKCloudServiceCapabilityAddToCloudMusicLibrary || capabilities==SKCloudServiceCapabilityMusicCatalogPlayback) {
NSLog(#"You CAN add to iCloud!");
} else {
NSLog(#"The ability to add Apple Music track is not there. sigh.");
}
}];
}];
You should check for the SKCloudServiceCapabilityMusicCatalogPlayback flag in capabilities. The code in your question checks whether the capabilities variable equals that flag, but you need to use bitwise operators. Here is how you'll do that.
SKCloudServiceController *controller = [SKCloudServiceController new];
[controller requestCapabilitiesWithCompletionHandler:^(SKCloudServiceCapability capabilities, NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Error getting SKCloudServiceController capabilities: %#", error);
} else if (capabilities & SKCloudServiceCapabilityMusicCatalogPlayback) {
// The user has an active subscription
} else {
// The user does *not* have an active subscription
}
}];
You can tell that SKCloudServiceCapabilityMusicCatalogPlayback is a flag and not just a regular constant value because the value uses bitwise operators (the "<<" shown in Apple's documentation).
SKCloudServiceCapabilityMusicCatalogPlayback = 1 << 0
https://developer.apple.com/reference/storekit/skcloudservicecapability/skcloudservicecapabilitymusiccatalogplayback?language=objc
I have made a helper singleton Class to implement Game Center. Then I am calling the method to authenticate the player in my AppDelegate.
If I start the app, all good!
Next step: I want to report my scores. I have this method as report Score
+ (void) reportScore: (Float64) score forIdentifier: (NSString*) identifier
{
GKScore* highScore = [[GKScore alloc] initWithLeaderboardIdentifier:identifier];
highScore.value = score;
[GKScore reportScores:#[highScore] withCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Error in reporting scores: %#", error);
}
}];
}
I call it when I have GameOver and shortly before switching to my GameOverScene:
[GCTurnBasedMatchHelperr reportScore:self.scoring forIdentifier:#"score"];
I am playing with a Sandbox Test User. If I go to game center then to the leaderboard of my game, it says: No Scores.
After that, I would like to display the leaderboard and the scores somehow.
If you need any more code, go to this github: https://gist.github.com/anonymous/1881303471cb9790af0b
Can anyone help me out?
Okay, I did a really simple mistake, I just used a wrong identifier when I report the score. It is not score it was Score.. .
Can we access the heart rate directly from the apple watch? I know this is a duplicate question, but no one has asked this in like 5 months. I know you can access it from the Health App but I'm not sure how "real-time" that will be.
Heart Rate Raw Data information is now available in Watchkit for watchOS 2.0.
WatchOS 2 includes many enhancements to other existing frameworks such as HealthKit, enabling access to the health sensors that access heart rate and health information in real-time.
You could check this information in the following session which is total 30 minutes presentation.If you do not want to watch entire session, then you directly jump to Healthkit API features which is in between 25-28 min:
WatchKit for watchOS 2.0 Session in WWDC 2015
Here is the source code implementation link
As stated in the HKWorkout Class Reference:
The HKWorkout class is a concrete subclass of the HKSample class.
HealthKit uses workouts to track a wide range of activities. The
workout object not only stores summary information about the activity
(for example, duration, total distance, and total energy burned), it
also acts as a container for other samples. You can associate any
number of samples with a workout. In this way, you can add detailed
information relevant to the workout.
In that given link, the following part of the code defines sample rate of heartRate
NSMutableArray *samples = [NSMutableArray array];
HKQuantity *heartRateForInterval =
[HKQuantity quantityWithUnit:[HKUnit unitFromString:#"count/min"]
doubleValue:95.0];
HKQuantitySample *heartRateForIntervalSample =
[HKQuantitySample quantitySampleWithType:heartRateType
quantity:heartRateForInterval
startDate:intervals[0]
endDate:intervals[1]];
[samples addObject:heartRateForIntervalSample];
As they state there:
You need to fine tune the exact length of your associated samples
based on the type of workout and the needs of your app. Using 5 minute
intervals minimizes the amount of memory needed to store the workout ,
while still providing a general sense of the change in intensity over
the course of a long workout. Using 5 second intervals provides a
much-more detailed view of the workout, but requires considerably more
memory and processing.
After exploring HealthKit and WatchKit Extension, My findings are as follows:
We do not need the WatchKit Extension to get the Heart Rate Data.
You just need to have an iPhone with paired Apple watch (which is obvious)
The Default Apple Watch Heart Rate monitor app updates the HealthKit data immediately only when it is in the foreground.
When the Default Apple Watch Heart Rate monitor app is in the Background, it updates the HealthKit data at the interval of 9-10 mins.
To get the Heart rate data from the HealthKit following query needs to be fired periodically.
func getSamples() {
let heathStore = HKHealthStore()
let heartrate = HKQuantityType.quantityType(forIdentifier: .heartRate)
let sort: [NSSortDescriptor] = [
.init(key: HKSampleSortIdentifierStartDate, ascending: false)
]
let sampleQuery = HKSampleQuery(sampleType: heartrate!, predicate: nil, limit: 1, sortDescriptors: sort, resultsHandler: resultsHandler)
heathStore.execute(sampleQuery)
}
func resultsHandler(query: HKSampleQuery, results: [HKSample]?, error: Error?) {
guard error == nil else {
print("cant read heartRate data", error!)
return
}
guard let sample = results?.first as? HKQuantitySample else { return }
// let heartRateUnit: HKUnit = .init(from: "count/min")
// let doubleValue = sample.quantity.doubleValue(for: heartRateUnit)
print("heart rate is", sample)
}
Please update me if anyone gets more information.
Happy Coding.
Update
I've updated your code to be clear and general, and be aware that you need to get authorization for reading HeathKit data and adding info.plist key Privacy - Health Records Usage Description
There is no direct way to access any sensors on the Apple Watch. You will have to rely on access from HealthKit.
An Apple evangelist said this
It is not possible to create a heart monitor app at this time. The
data isn't guaranteed to be sent to iPhone in real-time, so you won't
be able to determine what's going on in any timely fashion.
See https://devforums.apple.com/message/1098855#1098855
You can get heart rate data by starting a workout and query heart rate data from healthkit.
Ask for premission for reading workout data.
HKHealthStore *healthStore = [[HKHealthStore alloc] init];
HKQuantityType *type = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKQuantityType *type2 = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceWalkingRunning];
HKQuantityType *type3 = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierActiveEnergyBurned];
[healthStore requestAuthorizationToShareTypes:nil readTypes:[NSSet setWithObjects:type, type2, type3, nil] completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"health data request success");
}else{
NSLog(#"error %#", error);
}
}];
In AppDelegate on iPhone, respond this this request
-(void)applicationShouldRequestHealthAuthorization:(UIApplication *)application{
[healthStore handleAuthorizationForExtensionWithCompletion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(#"phone recieved health kit request");
}
}];
}
Then implement Healthkit Delegate:
-(void)workoutSession:(HKWorkoutSession *)workoutSession didFailWithError:(NSError *)error{
NSLog(#"session error %#", error);
}
-(void)workoutSession:(HKWorkoutSession *)workoutSession didChangeToState:(HKWorkoutSessionState)toState fromState:(HKWorkoutSessionState)fromState date:(NSDate *)date{
dispatch_async(dispatch_get_main_queue(), ^{
switch (toState) {
case HKWorkoutSessionStateRunning:
//When workout state is running, we will excute updateHeartbeat
[self updateHeartbeat:date];
NSLog(#"started workout");
break;
default:
break;
}
});
}
Now it's time to write **[self updateHeartbeat:date]**
-(void)updateHeartbeat:(NSDate *)startDate{
//first, create a predicate and set the endDate and option to nil/none
NSPredicate *Predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:nil options:HKQueryOptionNone];
//Then we create a sample type which is HKQuantityTypeIdentifierHeartRate
HKSampleType *object = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
//ok, now, create a HKAnchoredObjectQuery with all the mess that we just created.
heartQuery = [[HKAnchoredObjectQuery alloc] initWithType:object predicate:Predicate anchor:0 limit:0 resultsHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *sampleObjects, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {
if (!error && sampleObjects.count > 0) {
HKQuantitySample *sample = (HKQuantitySample *)[sampleObjects objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
NSLog(#"%f", [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]]);
}else{
NSLog(#"query %#", error);
}
}];
//wait, it's not over yet, this is the update handler
[heartQuery setUpdateHandler:^(HKAnchoredObjectQuery *query, NSArray<HKSample *> *SampleArray, NSArray<HKDeletedObject *> *deletedObjects, HKQueryAnchor *Anchor, NSError *error) {
if (!error && SampleArray.count > 0) {
HKQuantitySample *sample = (HKQuantitySample *)[SampleArray objectAtIndex:0];
HKQuantity *quantity = sample.quantity;
NSLog(#"%f", [quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]]);
}else{
NSLog(#"query %#", error);
}
}];
//now excute query and wait for the result showing up in the log. Yeah!
[healthStore executeQuery:heartQuery];
}
You also have a turn on Healthkit in capbilities. Leave a comment below if you have any questions.
I'm writing an application that will take several images from URL's, turn them into a UIImage and then add them to the photo library and then to the custom album. I don't believe its possible to add them to a custom album without having them in the Camera Roll, so I'm accepting it as impossible (but it would be ideal if this is possible).
My problem is that I'm using the code from this site and it does work, but once it's dealing with larger photos it returns a few as 'Write Busy'. I have successfully got them all to save if I copy the function inside its own completion code and then again inside the next one and so on until 6 (the most I saw it take was 3-4 but I don't know the size of the images and I could get some really big ones) - this has lead to the problem that they weren't all included in the custom album as they error'd at this stage too and there was no block in place to get it to repeat.
I understand that the actual image saving is moved to a background thread (although I don't specifically set this) as my code returns as all done before errors start appearing, but ideally I need to queue up images to be saved on a single background thread so they happen synchronously but do not freeze the UI.
My code looks like this:
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:singleImage]]];
[self.library saveImage:image toAlbum:#"Test Album" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
NSLog(#"Error");
}
}];
I've removed the repetition of the code otherwise the code sample would be very long! It was previously where the NSLog code existed.
For my test sample I am dealing with 25 images, but this could easily be 200 or so, and could be very high resolution, so I need something that's able to reliably do this over and over again without missing several images.
thanks
Rob
I've managed to make it work by stripping out the save image code and moving it into its own function which calls itself recursively on an array on objects, if it fails it re-parses the same image back into the function until it works successfully and will display 'Done' when complete. Because I'm using the completedBlock: from the function to complete the loop, its only running one file save per run.
This is the code I used recursively:
- (void)saveImage {
if(self.thisImage)
{
[self.library saveImage:self.thisImage toAlbum:#"Test Album" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
[self saveImage];
}
else
{
[self.imageData removeObject:self.singleImageData];
NSLog(#"Success!");
self.singleImageData = [self.imageData lastObject];
self.thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.singleImageData]]];
[self saveImage];
}
}];
}
else
{
self.singleImageData = nil;
self.thisImage = nil;
self.imageData = nil;
self.images = nil;
NSLog(#"Done!");
}
}
To set this up, I originally used an array of UIImages's but this used a lot of memory and was very slow (I was testing up to 400 photos). I found a much better way to do it was to store an NSMutableArray of URL's as NSString's and then perform the NSData GET within the function.
The following code is what sets up the NSMutableArray with data and then calls the function. It also sets the first UIImage into memory and stores it under self.thisImage:
NSEnumerator *e = [allDataArray objectEnumerator];
NSDictionary *object;
while (object = [e nextObject]) {
NSArray *imagesArray = [object objectForKey:#"images"];
NSString *singleImage = [[imagesArray objectAtIndex:0] objectForKey:#"source"];
[self.imageData addObject:singleImage];
}
self.singleImageData = [self.imageData lastObject];
self.thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.singleImageData]]];
[self saveImage];
This means the rest of the getters for UIImage can be contained in the function and the single instance of UIImage can be monitored. I also log the raw URL into self.singleImageData so that I can remove the correct elements from the array to stop duplication.
These are the variables I used:
self.images = [[NSMutableArray alloc] init];
self.thisImage = [[UIImage alloc] init];
self.imageData = [[NSMutableArray alloc] init];
self.singleImageData = [[NSString alloc] init];
This answer should work for anyone using http://www.touch-code-magazine.com/ios5-saving-photos-in-custom-photo-album-category-for-download/ for iOS 6 (tested on iOS 6.1) and should result in all pictures being saved correctly and without errors.
If saveImage:toAlbum:withCompletionBlock it's using dispatch_async i fear that for i/o operations too many threads are spawned: each write task you trigger is blocked by the previous one (bacause is still doing I/O on the same queue), so gcd will create a new thread (usually dispatch_async on the global_queue is optimized by gcd by using an optimized number of threads).
You should either use semaphores to limit the write operation to a fixed number at the same time or use dispatch_io_ functions that are available from iOS 5 if i'm not mistaken.
There are plenty example on how to do this with both methods.
some on the fly code for giving an idea:
dispatch_semaphore_t aSemaphore = dispatch_semaphore_create(4);
dispatch_queue_t ioQueue = dispatch_queue_create("com.customqueue", NULL);
// dispatch the following block to the ioQueue
// ( for loop with all images )
dispatch_semaphore_wait(aSemaphore , DISPATCH_TIME_FOREVER);
[self.library saveImage:image
toAlbum:#"Test Album"
withCompletionBlock:^(NSError *error){
dispatch_semaphore_signal(aSemaphore);
}];
so every time you will have maximum 4 saveImage:toAlbum, as soon as one completes another one will start.
you have to create a custom queue, like above (the ioQueue) where to dispatch the code that does the for loop on the images, so when the semaphore is waiting the main thread is not blocked.
I created a 'mirror'-like view in my app that uses the front camera to show a 'mirror' to the user. The problem I'm having is that I have not touched this code in weeks (and it did work then) but now I'm testing it again and it's not working. The code is the same as before, there are no errors coming up, and the view in the storyboard is exactly the same as before. I have no idea what is going on, so I was hoping that this website would help.
Here is my code:
if([UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront]) {
//If the front camera is available, show the camera
AVCaptureSession *session = [[AVCaptureSession alloc] init];
AVCaptureOutput *output = [[AVCaptureStillImageOutput alloc] init];
[session addOutput:output];
//Setup camera input
NSArray *possibleDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
//You could check for front or back camera here, but for simplicity just grab the first device
AVCaptureDevice *device = [possibleDevices objectAtIndex:1];
NSError *error = nil;
// create an input and add it to the session
AVCaptureDeviceInput* input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; //Handle errors
//set the session preset
session.sessionPreset = AVCaptureSessionPresetHigh; //Or other preset supported by the input device
[session addInput:input];
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];
//Now you can add this layer to a view of your view controller
[cameraView.layer addSublayer:previewLayer];
previewLayer.frame = self.cameraView.bounds;
[session startRunning];
if ([session isRunning]) {
NSLog(#"The session is running");
}
if ([session isInterrupted]) {
NSLog(#"The session has been interupted");
}
} else {
//Tell the user they don't have a front facing camera
}
Thank You in advanced.
Not sure if this is the problem but there is an inconsistency between your code and the comments. The inconsistency is with the following line of code:
AVCaptureDevice *device = [possibleDevices objectAtIndex:1];
In the comment above it says: "...for simplicity just grab the first device". However, the code is grabbing the second device, NSArray is indexed from 0. I believe the comment should be corrected as I think you are assuming the front camera will be the second device in the array.
If you are working on the assumption that the first device is the back camera and the second device is the front camera then this is a dangerous assumption. It would be much safer and more future proof to check the list of possibleDevices for the device that is the front camera.
The following code will enumerate the list of possibleDevices and create input using the front camera.
// Find the front camera and create an input and add it to the session
AVCaptureDeviceInput* input = nil;
for(AVCaptureDevice *device in possibleDevices) {
if ([device position] == AVCaptureDevicePositionFront) {
NSError *error = nil;
input = [AVCaptureDeviceInput deviceInputWithDevice:device
error:&error]; //Handle errors
break;
}
}
Update: I have just cut and pasted the code exactly as it is in the question into a simple project and it is working fine for me. I am seeing the video from the front camera. You should probably look elsewhere for the issue. First, I would be inclined to check the cameraView and associated layers.