I am trying to authenticate a LocalPLayer but I get errors and I can't figure out how to fix them. I am getting this off the Apple Developers forums and it has errors.
"no visible #interface for ViewController2 declares the selector disable gamcenter"
No visible #interface for 'ViewController2' declares the selector
'showAuthenticationDialogWhenReasonable:'
No visible #interface for 'ViewController2' declares the selector 'authenticatedPlayer:'
- (void) authenticateLocalPlayer
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
if (viewController != nil)
{
[self showAuthenticationDialogWhenReasonable: viewController];
}
else if (localPlayer.isAuthenticated)
{
[self authenticatedPlayer: localPlayer];
}
else
{
[self disableGameCenter];
}
}];
}
The code you've written in your question was lifted directly from Apple's documentation (specifically the "Authenticating A Local Player" section).
As the comment in that code says:
showAuthenticationDialogWhenReasonable: is an example method name.
Create your own method that displays an authentication view when
appropriate for your app.
and
authenticatedPlayer: is an example method name. Create your own method
that is called after the loacal player is authenticated.
and
"disableGameCenter" is the same.
All of them refer to "self", which means your code is looking for those implementations within the ViewController2 object.
Related
I am having a hard time as a beginner to Objective-C with learning how and when a function is being called, as I am not seeing it explicitly stated. Below is some code for logging into, and playing a song from the Spotify SDK that I found online.
#import "AppDelegate.h"
#interface AppDelegate ()
#property (nonatomic, strong) SPTAuth *auth;
#property (nonatomic, strong) SPTAudioStreamingController *player;
#property (nonatomic, strong) UIViewController *authViewController;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.auth = [SPTAuth defaultInstance];
self.player = [SPTAudioStreamingController sharedInstance];
// The client ID you got from the developer site
self.auth.clientID = #"5bd669abf2a14fb59839c2c0570843fe";
// The redirect URL as you entered it at the developer site
self.auth.redirectURL = [NSURL URLWithString:#"spotlightmusic://returnafterlogin"];
// Setting the `sessionUserDefaultsKey` enables SPTAuth to automatically store the session object for future use.
self.auth.sessionUserDefaultsKey = #"current session";
// Set the scopes you need the user to authorize. `SPTAuthStreamingScope` is required for playing audio.
self.auth.requestedScopes = #[SPTAuthStreamingScope];
// Become the streaming controller delegate
self.player.delegate = self;
// Start up the streaming controller.
NSError *audioStreamingInitError;
NSAssert([self.player startWithClientId:self.auth.clientID error:&audioStreamingInitError],
#"There was a problem starting the Spotify SDK: %#", audioStreamingInitError.description);
// Start authenticating when the app is finished launching
dispatch_async(dispatch_get_main_queue(), ^{
[self startAuthenticationFlow];
});
return YES;
}
- (void)startAuthenticationFlow
{
// Check if we could use the access token we already have
if ([self.auth.session isValid]) {
// Use it to log in
[self.player loginWithAccessToken:self.auth.session.accessToken];
} else {
// Get the URL to the Spotify authorization portal
NSURL *authURL = [self.auth spotifyWebAuthenticationURL];
// Present in a SafariViewController
self.authViewController = [[SFSafariViewController alloc] initWithURL:authURL];
[self.window.rootViewController presentViewController:self.authViewController animated:YES completion:nil];
}
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary *)options
{
// If the incoming url is what we expect we handle it
if ([self.auth canHandleURL:url]) {
// Close the authentication window
[self.authViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
self.authViewController = nil;
// Parse the incoming url to a session object
[self.auth handleAuthCallbackWithTriggeredAuthURL:url callback:^(NSError *error, SPTSession *session) {
if (session) {
// login to the player
[self.player loginWithAccessToken:self.auth.session.accessToken];
}
}];
return YES;
}
return NO;
}
- (void)audioStreamingDidLogin:(SPTAudioStreamingController *)audioStreaming
{
[self.player playSpotifyURI:#"spotify:track:3DWOTqMQGp5q75fnVsWwaN" startingWithIndex:0 startingWithPosition:0 callback:^(NSError *error) {
if (error != nil) {
NSLog(#"*** failed to play: %#", error);
return;
}
}];
}
#end
I am wondering how exactly these functions are being called sequentially, and specifically how the audioStreamingDidLogin one is being run.
Additionally I was wondering how it would look to call that function from the view controller with some sort of input coming from the UI.
Any help with this logic would be greatly appreciated! Thanks.
Your question is closely tied to the Spotify framework being used. It is not a question of when Objective-C is executing something - the language has a standard sequential execution model - but how the framework is doing callbacks, e.g. audioStreamingDidLogin, to your code and utilising threads/GCD to do concurrent execution.
First you should read the Spotify framework documentation.
You can also place a breakpoint at the start of each method and then run under the debugger. When a breakpoint is hit check which thread has stopped and the stack trace. That should give you a good idea of execution flow and the concurrent threads being used.
HTH
UIApplicationDelegate method application:didFinishLaunchingWithOptions: is called first, followed by application:openURL:options:.
That first app delegate method sets self as the delegate for an AudioStreamingController. This is how audioStreamingDidLogin gets called. You're telling the streaming controller, "Tell me (self) when interesting things happen". (See the SPTAudioStreamingControllerDelegate docs for what else it might tell you about).
You probably wouldn't (shouldn't) call this function directly, especially if there's a chance that you might call it before auth is complete. Doing so would likely result in an error on the call to playSpotifyURI. If you're certain that the user is authenticated, then you don't need to call it. Just call what it calls: playSpotifyURI.
Currently I'm working on an app that uses four protocols for communication between classes. Three are working fine, but one is still not working. I've set it up same as the others but the delegate is always losing its ID. I'm quite new to Objective-C so I can't get to the bottom of it. Here is what I did:
I have a MainViewController.h with the delegate
#property (weak, nonatomic) id <PlayerProtocol> player;
and a MainViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[[Interface sharedInstance] Init];
NSLog(#"Player ID: %#", _player);
NSLog(#"viewDidLoad: %#", self);
}
- (void)sedPlayer:(id) pointer{ //sed is no typo!
_player = pointer;
NSLog(#"sedPlayer ID: %#", _player);
NSLog(#"sedPlayer: %#", self);
}
+ (instancetype)sharedInstance {
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
In the Interface.m (NSObject)
- (void)Init {
[[MainViewController sharedInstance] sedPlayer:self];
}
And of course a protocol.h but this is not of interest as the delegate does the trouble! When I run the code I get the following output on the console:
sedPlayer ID: <Interface: 0x1700ab2e0>
sedPlayer: <MainViewController: 0x100406e30>
Player ID: (null)
viewDidLoad: <MainViewController: 0x100409550>
So it is obvious that the singleton is not working as the instance of the MainViewcontroller is different. For the singleton I'm using the dispatch_once standard method as I do with the other protocols that work fine. ARC is turned on. Does anyone has a clue what is wrong here and why the singleton is not working?
Here's how I think you ended up with two instances of the MainViewController. The first one, I assume, is created when navigating to the screen associated with MainViewController. The second one is created when you call [MainViewController sharedInstance] in Interface.m.
As the ViewController view is lazy loaded ("View controllers load their views lazily. Accessing the view property for the first time loads or creates the view controller’s views." from the Apple docs under ViewManagement), you see the viewDidLoad: <MainViewController: 0x100409550> log only once, when the first MainViewController gets navigated to and loads up the view.
Here's my suggestion:
Since you do the Interface initializing in the - (void)viewDidLoad, you might as well set self.player = [Interface sharedInstance].
The code would look something like this:
- (void)viewDidLoad {
[super viewDidLoad];
self.player = [Interface sharedInstance];
NSLog(#"Player ID: %#", _player);
NSLog(#"viewDidLoad: %#", self);
}
You should also get rid of - (void)sedPlayer:(id) pointer and + (instancetype)sharedInstance in your MainViewController. It is never a good idea to have a ViewController singleton, since you might end up messing up the navigation or having multiple states of it.
For a more in-depth article on avoiding singletons, you can check objc.io Avoiding Singleton Abuse
I have a problem with keeping a reference to a RPPreviewViewController in ReplayKit with ObjectiveC and I'm wondering what am I doing wrong.
The .h file:
#interface ReplayKitHelper : NSObject <RPPreviewViewControllerDelegate, RPScreenRecorderDelegate>
-(void)startRecording;
-(void)stopRecording;
-(void)previewRecording;
#property(strong) RPPreviewViewController* previewViewControllerRef;
#end
The .mm file:
#implementation ReplayKitHelper
#synthesize previewViewControllerRef;
-(void)startRecording
{
RPScreenRecorder* recorder = RPScreenRecorder.sharedRecorder;
recorder.delegate = self;
[recorder startRecordingWithMicrophoneEnabled : true handler : ^ (NSError *error)
{
}];
}
-(void)stopRecording
{
RPScreenRecorder* recorder = RPScreenRecorder.sharedRecorder;
[recorder stopRecordingWithHandler : ^ (RPPreviewViewController * previewViewController, NSError * error)
{
if (error == nil)
{
if (previewViewController != nil)
{
previewViewControllerRef = previewViewController;
}
}
}];
}
-(void)previewRecording
{
if (previewViewControllerRef != nil)
{
previewViewControllerRef.modalPresentationStyle = UIModalPresentationFullScreen;
previewViewControllerRef.previewControllerDelegate = self;
[[IOSAppDelegate GetDelegate].IOSController presentViewController : previewViewControllerRef animated : YES completion : nil];
// IOSController is my main UIViewController
}
}
#end
During the runtime I launch methods startRecording, stopRecording and previewRecording, in that order. Everything is going fine, until previewRecording, where it looks like the previewViewControllerRef is no longer valid (it's not nil, but it crashes when I'm trying to refer to it).
When I try to run [self previewRecording] inside the stopRecordingWithHandler block, after I pass the reference - everything works fine.
It looks like the previewViewController from the handler is released right after app leaves the block.
Most of the examples are written in Swift, unfortunatelly I'm condemned to ObjectiveC. In Swift examples the reference to previeViewController is just passed to the variable, but in ObjectiveC it seems to not working.
Do You have any ideas what's wrong here?
I'm going to assume that you are using ARC, if so there is no need to synthesize properties anymore.
Change your RPPreviewViewController in the Interface file to:
#property (nonatomic, strong) RPPreviewViewController *RPPreviewViewController;
Drop the #synthesize.
then in the stopRecording handler you can keep a reference to the available RPPreviewViewController like so:
- (void)stopScreenRecording {
RPScreenRecorder *sharedRecorder = RPScreenRecorder.sharedRecorder;
[sharedRecorder stopRecordingWithHandler:^(RPPreviewViewController *previewViewController, NSError *error) {
if (error) {
NSLog(#"stopScreenRecording: %#", error.localizedDescription);
}
if (previewViewController) {
self.previewViewController = previewViewController;
}
}];
}
In my experience ReplayKit is still buggy and there isn't a lot of documentation about it yet.
The following is my implemented GameCenter Leaderboards code...
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error){
if (viewController != nil) {
[self presentViewController:viewController animated:YES completion:nil];
LBs.hidden=NO;
}
else {
}
if ([GKLocalPlayer localPlayer].authenticated) {
GameCenter = YES;
}
else {
GameCenter = NO;
}
};
The above code is nothing special, it only lets you open up Game Center and check leaderboards. Then when you click on YOUR score and then SHARE button, it sends it to Twitter (for example). The message on Twitter is "Check Out my score on HighScore playing JungleJim".
However, I want a custom message that includes the HighScore number of the player. How do I change that Shared button to include a custom message on Twitter/Facebook accounts. I don't mean for the user to type up the message. I mean for the message to pop up already there with the highscore number.
Do I have to include code in the above code or somewhere else completely?
I am unfamiliar with game-center-leaderboard, but as for sharing on facebook with the FB iOS SDK, most likely what is implemented in the backend is a share dialog (https://developers.facebook.com/docs/sharing/ios#share_dialog). This as you can see doesn't allow, as CBroe suggested, for pre-populated text in the SDK's UI.
That being said, if there's a way in game-center-leaderboard to override this behavior and share using open graph stories, you could achieve a custom text like: Chris scored a high score using myApp
https://developers.facebook.com/docs/sharing/ios#open_graph
My view controller no longer gets deallocated after adding the following:
#property (strong, nonatomic) GKLocalPlayer *player;
(in viewDidLoad)
self.player = nil;
[self authenticatePlayer];
- (void)authenticatePlayer
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
__unsafe_unretained typeof(*localPlayer) *blockLocalPlayer = localPlayer;
localPlayer.authenticateHandler =
^(UIViewController *authenticateViewController, NSError *error)
{
if (authenticateViewController != nil)
{
[self presentViewController:authenticateViewController animated:YES
completion:nil];
}
else if (blockLocalPlayer.isAuthenticated)
{
self.player = blockLocalPlayer;
[self openGame];
}
else
{
// Disable Game Center
self.player = nil;
[self openGame];
}
};
}
- (void)setPlayer:(GKLocalPlayer *)player
{
_player = player;
NSString *playerName;
if (_player)
{
playerName = _player.alias;
}
else
{
playerName = #"Anonymous Player";
}
NSLog(#"%#", [NSString stringWithFormat:#"Welcome %#", playerName]);
}
The problem occurs whether or not the user connects to game center. There must be something in the code that is causing the view controller to remain in memory after it gets dismissed. If I comment these lines out:
self.player = nil;
[self authenticatePlayer];
the view controller will properly get deallocated when dismissed.
EDIT:
My hunch was correct. From Apple docs:
Game Kit maintains a strong reference to your completion handler even
after successfully authenticating a local player. If your game moves
into the background, Game Kit automatically authenticates the player
again whenever your game moves back to the foreground. Game Kit calls
your same completion handler each time it authenticates the local
player. Be mindful that in block programming, any Objective-C object
referenced inside a block is also strongly referenced by the block
until the block is released. Because Game Kit maintains a strong
reference to your completion handler until your game terminates, any
objects referenced from within your authentication handler are also
held indefinitely.
This is a problem for me though. I'm using Cocos2d and it has problems resetting its view unless the view controller is completely deallocated and created fresh.
Is there any way to get Game Kit to let go of my view controller?
Got sick of dealing with the issue and moved the GameKit authentication code to a different view controller, where it is not vital that it be deallocated.