13

I'm entering iOS via Sprite Kit, which I recognize is unwise.

My goal is to display a "Share" button upon Game Over. Tapping the share button should present a SLComposeViewController (Twitter Share). The contents of the scene should not change.

The game logic that dictates "Game Over" lives in SpriteMyScene.m, a subclass of SKScene.

I'm able to display a Share button on Game Over this way:

-(void)update:(CFTimeInterval)currentTime {

if (gameOver){
  UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
                [button addTarget:self
                           action:@selector(sendToController)
                forControlEvents:UIControlEventTouchDown];
                [button setTitle:@"Show View" forState:UIControlStateNormal];
                button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
                [self.view addSubview:button]; 
    }
}

- (void)sendToController
{
    NSLog(@"ok");
    SpriteViewController *viewController = [SpriteViewController alloc];
    [viewController openTweetSheet];
}

Where I get stuck is trying to get the showTweetButton method to work. My SpriteViewController.m looks like this:

- (void)openTweetSheet
    {
     SLComposeViewController *tweetSheet = [SLComposeViewController
                                           composeViewControllerForServiceType:
                                           SLServiceTypeTwitter];

    // Sets the completion handler.  Note that we don't know which thread the
    // block will be called on, so we need to ensure that any required UI
    // updates occur on the main queue
    tweetSheet.completionHandler = ^(SLComposeViewControllerResult result) {
        switch(result) {
                //  This means the user cancelled without sending the Tweet
            case SLComposeViewControllerResultCancelled:
                break;
                //  This means the user hit 'Send'
            case SLComposeViewControllerResultDone:
                break;
        }
    };

    //  Set the initial body of the Tweet
    [tweetSheet setInitialText:@"just setting up my twttr"];

    //  Adds an image to the Tweet.  For demo purposes, assume we have an
    //  image named 'larry.png' that we wish to attach
    if (![tweetSheet addImage:[UIImage imageNamed:@"larry.png"]]) {
        NSLog(@"Unable to add the image!");
    }

    //  Add an URL to the Tweet.  You can add multiple URLs.
    if (![tweetSheet addURL:[NSURL URLWithString:@"http://twitter.com/"]]){
        NSLog(@"Unable to add the URL!");
    }

    //  Presents the Tweet Sheet to the user
    [self presentViewController:tweetSheet animated:NO completion:^{
        NSLog(@"Tweet sheet has been presented.");
    }];
}

I always get something like this in the logs:

-[UIView presentScene:]: unrecognized selector sent to instance 0x13e63d00 2013-10-17 18:40:01.611 Fix[33409:a0b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView presentScene:]: unrecognized selector sent to instance 0x13e63d00'

Nick Barr
  • 554
  • 1
  • 7
  • 20
  • where's the presentScene: message send that the error message mentions? The code above doesn't seem to have any connection to the error. You're sending presentScene: to a regular UIView (one that's not a SKView). – CodeSmile Oct 17 '13 at 23:07
  • My understanding is that the trouble line is: [self presentViewController:tweetSheet animated:NO completion:^{ NSLog(@"Tweet sheet has been presented."); }]; but isn't that being done in the UIViewController? I don't understand the UIView part because SpriteMyScene.m is a subclass of SKScene and SpriteViewController.m is a subclass of UIViewController – Nick Barr Oct 17 '13 at 23:11

5 Answers5

11

You're creating a new view controller but never presenting it:

SpriteViewController *viewController = [SpriteViewController alloc];

I'm assuming that SpriteViewController is what presents your SpriteMyScene, and you'd like to hand control back to the presenting SpriteViewController.

You need to keep a reference to SpriteViewController in your SpriteMyScene subclass, and then access that reference when you call openTweetSheet.

in SpriteMyScene.h

@class SpriteViewController;

@interface SpriteMyScene : SKScene

@property (nonatomic, weak) SpriteViewController *spriteViewController;

@end

in SpriteViewController.m

// somewhere you initialize your SpriteMyScene object, I'm going to call it myScene

myScene.spriteViewController = self;

in SpriteMyScene.m

#import "SpriteViewController.h"

- (void)sendToController
{
    NSLog(@"ok");
    // use the already-created spriteViewController
    [_spriteViewController openTweetSheet];
}
paulrehkugler
  • 3,241
  • 24
  • 45
  • This doesn't work for me — Xcode doesn't like "myScene.spriteViewController = self;" (not found on object of type SKScene), and now it no longer calls the openTweetSheet method in the controller. – Nick Barr Oct 18 '13 at 00:29
  • You need to make sure your myScene object is explicitly typed as a SpriteMyScene object, not a generic SKScene object. – paulrehkugler Oct 18 '13 at 13:24
  • Got it, thanks. Part of the problem here was that I wasn't actually presenting the scene from the SpriteViewController. Instead, I was first presenting the "Welcome Menu" scene, which then did self.view presentScene:SpriteMyScene. Once I skipped the Welcome Menu and presented SpriteMyScene from the SpriteViewController I was able to get it working (which isn't ideal, since I'd still like to start with a Welcome Scene, but that's a separate problem). – Nick Barr Oct 18 '13 at 16:31
  • Yes, you could add a reference to the view controller in the Welcome Menu scene also, and when you create the SpriteMyScene, you can pass that reference along when you present the scene. – paulrehkugler Oct 18 '13 at 17:35
4

You can use

UIViewController *vc = self.view.window.rootViewController;

This code will give you the access to your root View controller so you can do anything you do will your view controller like normal.

However, do you need to add a button? Use a sprite and add an event to it is better for you in this case. Just call:

UIViewController *vc = self.view.window.rootViewController;
[vc openTweetSheet];

And

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
    for (SKNode *node in nodes) {
        if ([node.name isEqualToString:@"OpenTweet"]) {
            UIViewController *vc = self.view.window.rootViewController;
            [vc openTweetSheet];
        }
    }
}
Huygamer
  • 162
  • 1
  • 8
  • good answer, you could also do, if ([_tweetButton containsPoint:location]) to get your button click – DogCoffee Oct 18 '13 at 08:20
  • 1
    I dig this approach, but [vc openTweetSheet] says "no visible @interface for 'UIViewController' declares the selector 'openTweetSheet.' Is the problem that it's a SpriteViewController subclass? Or do I need to alloc it? I'm doing #import "SpriteViewController.h" and I have - (void)openTweetSheet; right before "@end" in SpriteViewController.h. – Nick Barr Oct 18 '13 at 16:08
3

If you want to open another UIViewController from inside your scene, you have to first create a delegate in the primary View Controller that originally created this scene so your scene can notify its ViewController of an open tweet action. You will need following steps:

  1. Define a delegate in the primary View Controller - the view controller of our scene, to handle the open tweet action.
  2. Implement the delegate method in the primary View Controller
  3. Add a delegate property in your scene so it can keep a handle to its ViewController's delegate method implementation. Set this delegate as the primary View Controller during the creation of the scene
  4. Detect an event on the scene to call the delegate method of the primary ViewController
  5. In the delegate method implementation pass the control to the TweetSheetViewController

Here's the example:

@protocol ViewControllerDelegate <NSObject>
-(void) openTweetSheet;
@end

Extend this ViewController to support this protocol in its .h file

@interface ViewController : UIViewController<ViewControllerDelegate>

@end

Then in the .m file implement the method from the protocol

-(void) openTweetSheet{
    TweetSheetViewController *ctrl = [[TweetSheetViewController alloc] initWithNibName:@"TweetSheetViewController" bundle:nil];

    [self presentViewController:ctrl animated:YES completion:nil];
}

In your Scene header class, add a delegate property

@interface MyScene : SKScene {

}
@property (nonatomic, weak) id <ViewControllerDelegate> delegate;

@end

In the ViewController, before presenting the scene set its delegate in viewDidLoad method:

// Create and configure the scene.
MyScene * scene = [MyScene sceneWithSize:skView.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;

[scene setDelegate:self];
// Present the scene.
[skView presentScene:scene];

Now your Scene can pass the message back to its ViewController and the ViewController can open another ViewController. In your scene class, determine the action which will trigger the opening up of TweetSheetViewController

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */

    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        SKAction *fadeOut = [SKAction fadeOutWithDuration:0.5];
        SKAction *fadeIn = [SKAction fadeInWithDuration:1.0];
        SKAction *sequence = [SKAction sequence:@[fadeOut,fadeIn]];

        SKNode *node = [self nodeAtPoint:location];
        if ([[node name]  isEqual: @"openTweet"]) {
            NSLog(@"help");
            [node runAction:sequence];
            [delegate openTweetSheet];
        }

}

Hope that helps.

vivsriva
  • 43
  • 5
1

Write the SlComposeViewController Method in the scene you want it to take place. For example:

@interface GameOverScene: SKScene

...initwithsize bla bla bla
...

Add these methods:

-(void)OpenTweetShet{

    if ([SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter]) {

    _composeTwitter = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
    [_composeTwitter setInitialText:@"TWEET"];

Then call:

self.view.window.rootViewController **to present the SLComposeViewController**

    [self.view.window.rootViewController presentViewController:_composeTwitter animated:YES completion:nil];


}

[_composeTwitter setCompletionHandler:^(SLComposeViewControllerResult result){

NSString *output =[[NSString alloc]init];

switch (result) {
    case SLComposeViewControllerResultCancelled:
        output = @"Post cancelled";
        break;
    case SLComposeViewControllerResultDone:
        output = @"Post Succesfull";
        break;
    default:
        break;      
    }

Here is the option for presenting UIAlert after the send/cancel post:

UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Twitter" message:output delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles: nil];
    [alert show];     
    }];
}
@end
4444
  • 3,541
  • 10
  • 32
  • 43
0

This worked for me: self.view.window.rootViewController

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSArray *nodes = [self nodesAtPoint:[touch locationInNode:self]];
for (SKNode *node in nodes) {
    if ([node.name isEqualToString:@"OpenTweet"]) {
        UIViewController *vc = self.view.window.rootViewController;
        [self.view.window.rootViewController openTweetSheet];
    }
}
}
user2887097
  • 309
  • 4
  • 12
  • This is essentially the same as [another answer](http://stackoverflow.com/a/19442912/1402846) posted months ago. – Pang May 23 '14 at 00:35