I'm trying to start a new GKGameSession and when I use createSession, all I can get so far is nil. Here's my code:
GKGameSession.createSession(inContainer: "test", withTitle: "MyGame", maxConnectedPlayers: 8)
{ (newGameSession, error) in
self.gameSession = newGameSession
print("\(newGameSession)")
newGameSession?.getShareURL(completionHandler: { (url, error) in
print("url: \(url) error: \(error)")
})
}
The only thing it prints is "nil". Any help is appreciated.
If you using the emulator, I suggest using a device instead. GKGameSessions do not play well with the emulator, because they depend on push notifications and an iCloud account that is logged in.
newGameSession is optional. So it seems like something has gone wrong when creating a new session.
I would say newGameSession is likely nil, in that case error will hopefully contain some useful information.
Try replacing print("\(newGameSession)") with print(newGameSession, error) to see what the error var has to say, or set a breakpoint if you know how to do that.
Try to use your app's iCloud container name instead of "test". The container name will be in the format iCloud.com.yourcompany.appname if you have selected the default container option. To ensure your app has an iCloud container you need to enable it in your app's Capabilities.
I'm a jumping in a little late, but I'd triple check the container name you're using to create the session.
I enabled all three options in xCode: key-value storage, iCloud documents and CloudKit.
I don't use iCloud drive.
The following code successfully creates a new session each time I send invites. It prints the new session, then iterates through all existing sessions for that container ID.
-(void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match
{
[self dismissViewControllerAnimated:YES completion:nil];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
NSString *iCloudContainerName = [#"iCloud." stringByAppendingString: bundleIdentifier];
[GKGameSession createSessionInContainer:iCloudContainerName
withTitle:#"test"
maxConnectedPlayers:4
completionHandler:^(GKGameSession * _Nullable session, NSError * _Nullable error)
{
NSLog(#"(1) Session: %#, Error: %#", session.identifier, [error description]);
[GKGameSession loadSessionsInContainer:iCloudContainerName
completionHandler:^(NSArray<GKGameSession *> * _Nullable sessions, NSError * _Nullable error)
{
for (GKGameSession *session in sessions)
{
NSLog(#"(2) Session: %#, Error: %#", session.identifier, [error description]);
}
NSLog(#"-----");
}];
}];
}
Related
i´m screen recording with ReplayKit and when the delegate method previewController(_:didFinishWithActivityTypes:) is called when i click the save button, it automatically saves into the camera roll. How can i change that? I want to save the video somwhere on the filesystem on device. I searched all over google and in apple documentation, but i couldn´t find anything relating to that.
If your app supports the app FILES, the replay kit can save file directly to the folder. For example VLC app has a folder in FILES. Otherwise you may need to implement an action extension to do the similar thing.
use this API
- (void)startCaptureWithHandler:(nullable void (^)(CMSampleBufferRef sampleBuffer, RPSampleBufferType bufferType, NSError *_Nullable error))captureHandler completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler API_AVAILABLE(ios(11.0), tvos(11.0), macos(11.0));
sample code:
-(void)startCapture {
if (#available(iOS 11.0, *)) {
[[RPScreenRecorder sharedRecorder] startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
if (CMSampleBufferDataIsReady(sampleBuffer) && bufferType == RPSampleBufferTypeVideo) {
NSLog(#"Recording started successfully.");
//save using AVAssetWriter
}
} completionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(#"Recording started successfully.");
}else{
NSLog(#"Recording started error %#",error);
}
}];
} else {
// earlier versions issue
}
}
Also: this post may help u somehow
AVAssetWriter Doc
I'm trying to incorporate GKGameSession into my Game Center game. I've tried several combinations of the following code: running the commands asynchronously, chaining them in the completion handlers, etc. Every time I see the same result: I can use saveData just fine until I've called getShareURLWithCompletionHandler. After that, any attempt to saveData throws an error.
Here's the simplest version of code that exhibits the problem:
CKContainer *defaultContainer = [CKContainer defaultContainer];
[GKGameSession createSessionInContainer:defaultContainer.containerIdentifier
withTitle:#"temp title"
maxConnectedPlayers:4
completionHandler:^(GKGameSession * _Nullable session, NSError * _Nullable error)
{
if (error)
{
[self printError:error];
}
[session getShareURLWithCompletionHandler:^(NSURL * _Nullable url, NSError * _Nullable error)
{
if (error)
{
[self printError:error];
}
}];
NSData *newData = [NSData dataWithBytesNoCopy:#"abcdefghijklmnopqrstuvwxyz" length:26];
[reSession saveData:newData completionHandler:^(NSData * _Nullable conflictingData, NSError * _Nullable error)
{
if (error)
{
[self printError:error];
}
}];
}];
In most cases, the saveData call simply crashes:
malloc: *** error for object 0x32df14: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
But sometimes it throws an error:
GKGameSessionErrorDomain:GKGameSessionErrorUnknown
I've tried different kinds of data being saved. I've tried making the calls sequential by chaining all the calls in completion handlers. I've tried doing the URL fetch and data save inside and outside of the creationSession completion handler.
Is there something I'm doing wrong here?
I see the same, but with a more useful error:
The requested operation could not be completed because the session has been updated on the server, causing a conflict.
The save documentation says,
It is up to the developer to decide how to handle save conflicts.
Here though, retrying the save fails every time, forever. So yeah, that's the same state you're in.
However, when the player joining the game enters the URL on their device, their GKGameSessionEventListener's didAddPlayer: is called, and then if they save... they get the same conflict error, but if they then retry the save... it works!
The player creating the link is locked out of saving or updating game state, until joining players have updated the data. When the other player saves, the original player gets a call to session:player:didSave: on the GKGameSessionEventListener.
At that point the original player can then save as expected.
You should put one block inside other. Because blocks may be completed in any order.
I have working code like this:
NSData *newData = [NSData dataWithBytesNoCopy:#"abcdefghijklmnopqrstuvwxyz" length:26];
[reSession saveData:newData completionHandler:^(NSData * _Nullable conflictingData, NSError * _Nullable error)
{
if (error)
{
[self printError:error];
} else {
[session getShareURLWithCompletionHandler:^(NSURL * _Nullable url, NSError * _Nullable error)
{
if (error)
{
[self printError:error];
}
}];
}
}];
So I am trying to allow two users to swap files that each has in their Google Drive. That involves knowing the ID of the other person's file and using the API calls to retrieve it. Both files sit in folders that have been shared to anyone/public.
Trouble is when I execute the code below I am finding that each user can only use the downloadUrl corresponding to the file they own - the others return a 404. In this case either "mine" or "theirs" depending on the account I'm logged into.
// _driveService and its authorizer setup elsewhere
// Retrieve the metadata then the actual data
NSString *mine = #"0B4Pba9IBDsR3T1NVTC1XSGJTenc";
NSString *theirs = #"0B4n9OyY8tqWpNlNaN1dUc3FsNG8";
NSString *get = [NSString stringWithFormat:#"https://www.googleapis.com/drive/v2/files/%#",theirs];
[_driveService fetchObjectWithURL:[NSURL URLWithString:get] completionHandler:^
(GTLServiceTicket *ticket, GTLDriveFile *file, NSError *error)
{
if (error != nil)
NSLog(#"Error retrieving metadata: %#", error);
else
{
// Download the actual data
GTMHTTPFetcher *fetcher = [_driveService.fetcherService fetcherWithURLString:file.downloadUrl];
[fetcher beginFetchWithCompletionHandler:^
(NSData *data, NSError *error)
{
if (error != nil)
NSLog(#"Error retrieving actual data: %#", error);
else
{
NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Content: %#", content);
}
}];
}
}];
Error retrieving actual data: Error Domain=com.google.HTTPStatus Code=404 "The operation couldn’t be completed. (com.google.HTTPStatus error 404.)"
What am I doing wrong here? If it's a permissions thing, why am I allowed to get the metadata?
Note this is for an iOS app and both files were created and uploaded from the app using the official client API (rev 353).
Hah, so it seems the devil is in the detail I left out of the question. When creating the authorizer the scope I was providing is kGTLAuthScopeDriveFile, which was the default in an example and I forgot all about it when everything else thus far worked fine. Apparently I need to use kGTLAuthScopeDrive instead (the differences are explained here)
The logic seems a bit flawed here though, I mean I don't want access to other files that weren't created with the app, I just want access to a public file somebody else created with the app...
A few customers of a Core Data based iOS application report that they occassionally lose data. The reports are very odd, which is the reason I'd like to ask for your take on this. The customers report that when they reopen the application after some time (minutes, hours, or next day), some of their data is lost as if the underlying database reverted to a previous state.
I have been working with Core Data for several years and have never run in an issue like this before. The application is fairly simple, which means I only use one managed object context and the changes are committed before the application goes to the background.
I realize that this is a long shot, but what could be some potential causes of this type of problem or what checks can I make to gather more information? Unfortunately, I cannot reproduce the issue myself, which would make all this a lot easier.
Update:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator) return _persistentStoreCoordinator;
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Prime.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:#{ NSMigratePersistentStoresAutomaticallyOption : #YES, NSInferMappingModelAutomaticallyOption : #YES } error:&error]) {
// Error Handling
}
return _persistentStoreCoordinator;
}
Check to see if you have put the save message in the appropriate appDelegate methods so that there is not a way to resign the application without saving. applicationWillResignActive and applicationWillTerminate should cover all of your needs.
Aside from that proper error handling and logging should gain you a lot of ground. I personally like to log these types of errors to a file that can be sent to me upon request. But that might be overkill for your particular application. This is written from memory so forgive any errors.
NSError *error = nil;
if (![[self managedObjectContext] save:&error])
{
NSString *errorString = #"%# ERROR : %# %#", [[NSDate date] description], [error localizedDescription], [error userInfo]);
NSString *documentsDirectory = [NSHomeDirectory()
stringByAppendingPathComponent:#"Documents"];
NSString *filePath = [documentsDirectory
stringByAppendingPathComponent:#"errorLog.txt"];
// Write to the file
[errorString writeToFile:filePath atomically:YES
encoding:NSUTF8StringEncoding error:&error];
}
You should check if save: method reports any error, for example like this:
NSError *error = nil;
if (![[self managedObjectContext] save:&error])
{
NSLog(#"Error %#", action, [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
// if there is a detailed errors - we can dump all of them
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError localizedDescription]);
}
}
else { // in other case lets dump all the info we have about the error
NSLog(#" %#", [error userInfo]);
}
}
One of the most common fail reasons is validation error, for example, it may expect a field to be presented while user haven't entered it or it may expect the value to be less than XX characters and so on.
Basically that means if it takes too long to provide the client with new build with debug info you can ask them to send you example of data they use to enter.
I can't be sure this is the reason, but it could be that when the app goes to the background, for some reason sometimes the maximum allowed time to handle this (backgroundTimeRemaining) is exceeded. From the Apple docs:
This property contains the amount of time the application has to run in the background before it may be forcibly killed by the system. While the application is running in the foreground, the value in this property remains suitably large. If the application starts one or more long-running tasks using the beginBackgroundTaskWithExpirationHandler: method and then transitions to the background, the value of this property is adjusted to reflect the amount of time the application has left to run.
If your app gets killed because saving the context takes too long, Core Data may decide to restore the previous state to at least get something consistent. If you can get log results from some users reporting this issue, you could try logging the property value after having tried to save the context, to check this.
Any chance I can save/update matchdata even when it is not my turn?
[currentMatch saveCurrentTurnWithMatchData:data completionHandler:^(NSError *error) {
if (error)
{ }];
The above code can be used if it is still this user's turn, but what if it is not this user's turn? How do I send data between two players?
As of iOS 6.0, you cannot. :(
You can save match data without advancing the turn (assuming you are
the current player). see - saveCurrentTurnWithMatchData:completionHandler:
You can end a game out of turn. see - participantQuitOutOfTurnWithOutcome:withCompletionHandler:
However, you cannot update match data out of turn.
GKTurnBasedMatch Reference
Try this
- (void) advanceTurn
{
NSData *updatedMatchData = [this.gameData encodeMatchData];
NSArray *sortedPlayerOrder = [this.gameData encodePlayerOrder];
this.MyMatch.message = [this.gameData matchAppropriateMessage];
[this.myMatch endTurnWithNextParticipants: sortedPlayerOrder turnTimeOut: GKTurnTimeoutDefault
matchData: updatedMatchData completionHandler ^(NSError *error) {
if (error)
{
// Handle the error.
}
}];
}