I am creating a game on XCode that has a menu with buttons for setup conditions (i.e., "Play to 30", "Play to 20", etc.). I want these buttons to create a segue to the same ViewController that has my game, with the only difference being how many points must be achieved until the game is over. It is far too inefficient to have multiples of the same ViewController for each setting. Is there a way around this?
In your game view controller create a custom initializer in as so:
// add in GameViewController.m
#implementation GameViewController
-(id)initWithLimit:(int)limit {
self = [super initWithNibName:#"NibName" bundle:nil];
if (self) {
_limit = limit;
}
return self;
}
// add in GameViewController.h
#interface GameViewController : UIViewController
#property (nonatomic) int limit;
#end
implement the menu's button actions as so:
-(IBAction)play30 {
GameViewController *game = [[GameViewController alloc] initWithLimit:30];
// Handle game view here.
}
this answer assumes you create a new instance of GameViewController when the user taps the button.
if you don't want to instantiate a new ViewControllerSubclass each time the button is taped then you can create a GameViewController property in you menu view controller and use lazy instantiation for the game view controller:
- (GameViewController *)game {
if (!_game) _game = ...;
return _game;
}
-(IBAction)play20 {
// Assuming game is a property.
self.game.limit = 20;
// Perform setup that expects the limit property to be set.
[self.game setup];
// Handle game view here.
}
Hope this helped :)
Related
I'm new to Objective-C and have a question. Did the search multiple times but I couldn't find what I was looking for.
I'm using storyboard for this app. On the homescreen you've got some buttons with labels above them. Those labels should tell a number. When pushing the button you go to a new viewController where you have input that (after 'save') goes back to the homescreen and updates the label with the correct number. All that works great for one button and I'm very happy about it.
The problems are:
1. Since I have multiple buttons with labels, I want to use the same viewController to give input over and over again. I tried connecting every button to slide to the viewController under the identifier "AddData", but Xcode doesn't allow the same identifiers twice or more in storyboard. So I would need something else for this. Any idea?
2. Currently I use the following code to bring back the data to the homescreen:
homeScreenViewController
- (IBAction)unwindToHomeScreen:(UIStoryboardSegue *)segue;
{
inputDataViewController *source = [segue sourceViewController];
self.logoOneLabel.text = source.endTotalNumber;
}
inputDataViewController:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if (sender != self.saveButton) {
return;
} else {
if (endTotalLabelNumber > 0) {
self.endTotalNumber = [NSString stringWithFormat:#"%.0f", totalLabelNumber + endTotalLabelNumber];
} else if (endTotalLabelNumber == 0 && totalLabelNumber == 0){
self.endTotalNumber = 0;
} else {
self.endTotalNumber = [NSString stringWithFormat:#"%.0f", totalLabelNumber + endTotalLabelNumber];
}
}
}
This works great for the one button, but how to use this with multiple? I heard about Delegates to use the same viewController multiple time and get data back to different places, but I just don't get it. Any help?
You shouldn't need delegates.
What you will need is a property on the view controller that handles input to it knows which button it is handling input for.
When you segue to the input controller, set this property, based on which button was pushed. When you unwind back, fetch this property to know which label to modify.
For example, in your input view controller's .h file, add a property like this:
#property (nonatomic,assign) NSInteger handlingTag;
Or something, whatever name makes sense to you.
Now you need to implement your home screen view controller's prepareForSegue:sender:.
Use the sender argument to determine which button was pushed, and based on that, set the input view controller's new handlingTag property based on the button in a way that you will know what to do with it when we unwind.
Now in the unwind method:
switch (source.handlingTag)
Create a switch structure based on the source's handlingTag property, and set the appropriate label based on this value.
As Jeff points out in the comments, it'd be a really good idea to define an NS_ENUM to use here for the property rather than an NSInteger. The NS_ENUM would allow you to name the values you're using.
There is a few different way to implement what you need. But i think most common its a delegate.
This is how your inputDataViewController looks like:
#import <UIKit/UIKit.h>
#protocol inputDataDelegate;
#interface inputDataViewController : UIViewController
#property (weak) id<inputDataDelegate> delegate;
#property (strong, nonatomic) NSNumber *buttonTag;
#end
#protocol inputDataDelegate <NSObject>
-(void) inputDataViewControllerDismissed:(id)data;
#end
Then in #implementation, you should in "save" button action, message to you delegate method :
[self inputDataViewControllerDismissed:#{#"buttonTag":buttonTag,#"endTotalNumber":endTotalNumber}
Next in homeScreenViewController connect delegate :
#interface homeScreenViewController : UIViewController<inputDataDelegate>
After that in #implementation:
-(void)inputDataViewControllerDismissed:(id)data
{
// if you use modal
[self dismissViewControllerAnimated:YES completion:nil];
// or if you use push
//[self.navigationController popViewControllerAnimated:YES];
switch (data[#"buttonTag"]) {
case 1:
self.lableWtiTagOne = data[#"endTotalNumber"];
break;
case 2:
self.lableWtiTagTwo = data[#"endTotalNumber"];
break;
// number of cases depend how many buttons you have
}
Also, most important, thing didn't forget send self to our delegate:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"inputDataController"])
{
inputDataViewController *inputCtrl = [segue destinationViewController];
inputCtrl.delegate = self;
inputCtrl.buttonTag = sender.tag
}
}
I have a scene where the user chooses level in a game, then i want to switch to a game scene with the level the user chose. I want to create the game scene with the right level but to do this i need to pass an integer which is the level-number to the game scene so i know which level to load.
My question is, how do i accomplish this? I have looked at the userdata property of nodes but could not figure out how to use it to accomplish what i want. Any help would be deeply appreciated.
When you call [self.view presentScene:scene transition:transition] inside of your level selection scene, first set up your new scene with the level number. SO:
//GameScene.h
//This is the public interface for your game scene
#import SpriteKit;
#interface GameScene : SKScene
#property (nonatomic, assing)NSInteger levelNumber;
#end
And then
//Level Selection Scen
//Select.m
#import "GameScene.h"
... Rest of your code
User selects level and you prepare for transition and call
- (void)transitionToLevel:(NSInteger)level{
GameScene *destinationScene = [[GameScene alloc] initWithSize:self.size];
destinationScene.levelNumber = level;
SKTransition *transition = //Set up transition here
[self.view presentScene:destinationScene transition:transition];
}
And in your game scene's implementation
//GameScene.m
#import "GameScene.h"
#implementation GameScene
-(id)initWithSize:(CGSize){
if (self.levelNumber == 1){
[self playLevelOne]; //Where this is where you play your game
}
else if (self.levelNumber == 2){
[self playLevelTwo];
}
}
- (void)playLevelOne{
//Level Implementation
}
- (void)playLevelTwo{
//Level Implementation
}
#end
You would do this for each level you plan to support
// First Scene
NSUserDefaults.standardUserDefaults().setInteger(12345, forKey:"SCORE")
// Second Scene
if let myScore: AnyObject = NSUserDefaults.standardUserDefaults().objectForKey("SCORE") {
println(myScore)
finalScore = myScore as IntegerLiteralType
}
I'm new to iOS development and I'm trying to make a simple arithmetic game. It has two views, a kind of start screen with a button to start playing the game and a label with the high score on it, and the view to play the actual game. Here is a screenshot of it:
The code for the game's ViewController looks like this:
#import "AdditionController.h"
#import "ViewController.h"
#interface AdditionController ()
//properties and outlets here
#end
#implementation AdditionController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setBackground];
self.difficulty = 20;
[self setupGame];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)setBackground
{
...
}
-(void)setupGame
{
...
}
-(void)writeQuestion
{
...
}
- (void)advanceTimer:(NSTimer *)timer
{
...
}
- (IBAction)checkAnswer:(id)sender {
...
}
- (IBAction)quit:(id)sender {
[self gameOver];
}
- (void)gameOver{
NSString *goodbye = [NSString stringWithFormat:#"You scored %d.", self.score_val];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// to store
NSNumber *aNumber = [defaults objectForKey:#"hiScore"];
NSInteger anInt = [aNumber intValue];
if (anInt < self.score_val) {
[defaults setObject:[NSNumber numberWithInt:self.score_val] forKey:#"hiScore"];
[defaults synchronize];
goodbye = [goodbye stringByAppendingString:#"\nThat's a new high score!"];
}
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Game Over!" message:goodbye delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
// I have been using this method to go back to the start screen.
// But this method doesn't update the new highscore
[self.navigationController popViewControllerAnimated:YES];
// However I should be able to do this if I can setup an ID for the start screen
ViewController *svc = [storyboard instantiateViewControllerWithIdentifier:#"StartViewController"];
}
#end
However when I go to View -> Utilities -> Show Identity Inspector, there is no option to set the story board ID, only one to set the restoration ID.
I was wondering if there was anyway to set the storyboard ID, or if I was missing something obvious? I'm using Xcode Version 5.0.2 (5A3005).
Also just wanted to add that I don't seem to be able to select UIViewController from the drop down list!
First, your screenshot clearly shows that you have the UIView selected in the storyboard, not the UIViewController, which is what you can set a storyboard ID on. So if you want to set an ID, you have to make sure you have the actual UIViewController selected.
Second, what you're trying to do by setting a storyboard ID is to create another instance of your first view controller, which is not what you want to do. You still have an instance of this view controller, you just want to get that instance and update the high score on it. You can do this in multiple ways:
You could create a delegate protocol on your second view controller, where it has a delegate method for updating the high score. Then you would set your first view controller as the delegate of the second, implement the delegate protocol method(s) in the first view controller, and call these at the appropriate time from the second view controller.
You could pass a block to the second view controller from the first that can be called with a high score (and any other important information) to let the first view controller know what to update.
You could use the UINavigationController to get the first view controller. If it is the root view controller of the navigation controller, then it'll be easy to get. Then you'll have your reference to the already existing instance and can update the high score however you were already planning on doing so. This one is the least ideal choice, however, because it requires that the second view controller have knowledge of the type of the first view controller, when really that shouldn't be necessary.
More just to add to Gavin's answer,
Whenever I've built simple games within an iOS app before i've created separate UIViews from the object library in storyboard, then I've put them outside the view controller's main view (i.e. move them below 'First Responder' in the document outline view). Then you can make these views properties of your viewController, and swap them in/out whenever you want with something like this when the start button is pressed to reveal self.gameView underneath:
[self.startView removeFromSuperview];
and the same would apply to reveal a self.finishView
i'm used to programming for iOS, and I've become very accustomed to the UIViewController. Now, i'm creating an OSX application and i'm having a few general questions on best practice.
In a UIViewController I generally setup my views in the -(void)viewDidLoad method - I don't actually create a custom UIView for the UIViewController unless it's really needed - so the UIViewController adds view to its own view, removes them, animates them and so forth - first off, is good practice?
And for my main question - what is the best practice in OSX? I like creating interfaces programatically and simply prefer it that way. If i, say create a new custom window and want to manage its view. What's the best way to do it, and where to i instantiate the user interface best?
Summary: How do i construct custom views programatically and set up a best-practice relationship between views and controllers in OSX? And is it considered good practice to use a view controller to create the views within its view?
Kind regards
To construct the view in code in an NSViewController, override loadView and be sure to set the view variable. Do not call super's implementation as it will attempt to load a nib from the nibName and nibBundle properties of the NSViewController.
-(void)loadView
{
self.view = [[NSView alloc] init];
//Add buttons, fields, tables, whatnot
}
For a NSWindowController, the procedure is very similar. You should call windowDidLoad at the end of your implementation of loadWindow. Also the window controller does not call loadWindow if the window is nil, so you will need to invoke it during init. NSWindowController seems to assume you will create the window in code before creating the controller except when loading from a nib.
- (id)initWithDocument:(FFDocument *)document
url:(NSURL *)url
{
self = [super init];
if (self)
{
[self loadWindow];
}
return self;
}
- (void)loadWindow
{
self.window = [[NSWindow alloc] init];
//Content view comes from a view controller
MyViewController * viewController = [[MyViewController alloc] init];
[self.window setContentView:viewController.view];
//Your viewController variable is about to go out of scope at this point. You may want to create a property in the WindowController to store it.
[self windowDidLoad];
}
Some optional fancification (10.9 and earlier)
Prior to 10.10, NSViewControllers were not in the first responder chain in OSX. The menu will automatically enable/disable menu items for you when an item is present in the responder chain. You may want to create your own subclass of NSView with an NSViewController property to allow it to add the controller to the responder chain.
-(void)setViewController:(NSViewController *)newController
{
if (viewController)
{
NSResponder *controllerNextResponder = [viewController nextResponder];
[super setNextResponder:controllerNextResponder];
[viewController setNextResponder:nil];
}
viewController = newController;
if (newController)
{
NSResponder *ownNextResponder = [self nextResponder];
[super setNextResponder: viewController];
[viewController setNextResponder:ownNextResponder];
}
}
- (void)setNextResponder:(NSResponder *)newNextResponder
{
if (viewController)
{
[viewController setNextResponder:newNextResponder];
return;
}
[super setNextResponder:newNextResponder];
}
Finally, I use a custom NSViewController that overrides setView to set the viewController property when I use my custom views.
-(void)setView:(NSView *)view
{
[super setView:view];
SEL setViewController = #selector(setViewController:);
if ([view respondsToSelector:setViewController])
{
[view performSelector:setViewController withObject:self];
}
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
And for my main question - what is the best practice in OSX? I like
creating interfaces programatically and simply prefer it that way. If
i, say create a new custom window and want to manage its view. What's
the best way to do it, and where to i instantiate the user interface
best?
All these are done in awakeFromNib and init.
The following code is creating many windows and storing them in array. For each window you can add views. And each view may contains all the controls you wish to have.
self.myWindow= [[NSWindow alloc] initWithContentRect:NSMakeRect(100,100,300,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[self.myWindowArray addObject:self.myWindow];
for (NSWindow *win in self.myWindowArray) {
[win makeKeyAndOrderFront:nil];
}
I am currently designing the structure for my first iPhone game and ran into a problem. Currently, I have a 'MenuViewController' that allows you to pick the level to play and a 'LevelViewController' where the level is played.
A UIButton on the 'MenuViewController' triggers a modal segue to the 'LevelViewController'.
A UIButton on the 'LevelViewController' triggers the following method to return to the 'MenuViewController':
-(IBAction)back:(id)sender //complete
{
[self dismissModalViewControllerAnimated:YES];
}
The problem is, I have a UILabel on the menu page that prints the number of total points a player has. Whenever I go back to the menu from the level, I want this label to automatically update. Currently, the label is defined programmatically in the 'MenuViewController':
-(void)viewDidLoad {
[super viewDidLoad];
CGRect pointsFrame = CGRectMake(100,45,120,20);
UILabel *pointsLabel = [[UILabel alloc] initWithFrame:pointsFrame];
[pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]];
[self.pointsLabel setTag:-100]; //pointsLabel tag is -100 for id purposes
}
self.playerPoints is an integer property of MenuViewController
Is there a way I could update the label? Thanks ahead of time!
This is a perfect case for delegation. When the LevelViewController is done, it needs to fire off a delegate method which is handled in the MenuViewController. This delegate method should dismiss the modal VC and then do whatever else you need it to do. The presenting VC should normally handled the dismissal of modal views it presents.
Here is a basic example of how to implement this:
LevelViewController.h (Above the Interface declaration):
#protocol LevelViewControllerDelegate
-(void)finishedDoingMyThing:(NSString *)labelString;
#end
Same file inside ivar section:
__unsafe_unretained id <LevelViewControllerDelegate> _delegate;
Same File below ivar section:
#property (nonatomic, assign) id <LevelViewControllerDelegate> delegate;
In LevelViewController.m file:
#synthesize delegate = _delegate;
Now in the MenuViewController.h, #import "LevelViewController.h" and declare yourself as a delegate for the LevelViewControllerDelegate:
#interface MenuViewController : UIViewController <LevelViewControllerDelegate>
Now inside MenuViewController.m implement the delegate method:
-(void)finishedDoingMyThing:(NSString *)labelString {
[self dismissModalViewControllerAnimated:YES];
self.pointsLabel.text = labelString;
}
And then make sure to set yourself as the delegate for the LevelViewController before presenting the modal VC:
lvc.delegate = self; // Or whatever you have called your instance of LevelViewController
Lastly, when you are done with what you need to do inside the LevelViewController just call this:
[_delegate finishedDoingMyThing:#"MyStringToPassBack"];
If this doesn't make sense, holler and I can try to help you understand.
Make a property self.pointsLabel that points to the UILabel, then you can just call something like [self.pointsLabel setText:[NSString stringWithFormat:#"Points: %i", self.playerPoints]]; to update the label with the new score
In your modal view header file, add the property:
#property (nonatomic,assign) BOOL updated;
Then in your main view controller, use didViewAppear with something like:
-(void)viewDidAppear:(BOOL)animated{
if (modalView.updated == YES) {
// Do stuff
modalView.updated = NO;
}
}
Where "modalView" is the name of that UIViewController that you probably alloc/init there.
Add more properties if you want to pass more info, like what level the user picked.