Programmatically switch view in iOS - ios

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

Related

Xcode using delegate to pass data between controllers

Hi im developing an app that has a parent view that then used containers to embed other views as seen below.
For now im only working with the left and centre container which are both table views. The main view or the Project screen view is my parent controller and i want it to pass data to and from the two child controller and i know for this the best option is to use delegates. However each example i have looked at that uses delegates, created and initialises a new view controller so for example lets say the left container embeds a view using the leftviewcontroller. Each example has this line of code.
LeftViewController *sampleProtocol = [[LeftViewController alloc]init];
LeftViewController.delegate = self;
Im thinking i dont need to create a new LeftViewController since it is embeded it is already in my list of child controllers. So my queston is how would i get the controller from the list of child controllers and set the parent as the delegate. I know i it is an array and i can use objectAtIndex but how do i know the order of items in the array will not change can i not call it but a tag or identifier? Thank you for any help sorry if the question is not that clear its my first time setting up delegates.
i know for this the best option is to use delegates.
In this case, I wouldn't be so sure. I think the best option would be to have a robust model and use KVO and notifications to signal updates between view controllers.
The direct answer to your question is not too bad.
for (UIViewController *viewController in self.childViewControllers) {
if ([viewController isKindOfClass:[LeftViewController class]]) {
LeftViewController *leftViewController = (id)viewController;
leftViewController.delegate = self;
break;
}
}
I think a minor improvement on this would be to use the segue. Make sure each of the containers have a named segue. In this example, the left view controller has a segue with the identifier "Load Child LeftViewController".
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Load Child LeftViewController"]) {
LeftViewController *leftViewController = segue.destinationViewController;
leftViewController.delefate = self;
}
}
Its always better to use NSNotificationCenter for such complex mechanism.
*** put following code in LeftController.m ***
// *** Register a Notification to recieve a Data when something happens in Center controller ***
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedNotification:)
name:#"hasSomeData"
object:nil];
// *** create a method to receive Notification data ***
- (void)receivedNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"hasSomeData"])
{
// do your stuff here with data
NSLog(#"data %#",[notification object]);
}
}
*** when something happen in center controller post a notification to inform Left Controller ***
[[NSNotificationCenter defaultCenter] postNotificationName:#"hasSomeData" object:self];
//Secondvc.h
#protocol Sendmessage<NSObject>
#required
-(void)Object:(NSArray *)tosend;
#end
#interface Secondvc:UIViewcontroller{
id <Sendmessage> delegate;
}
#property(strong,nonatomic) id <Sendmessage> delegate;
#end
//Secondvc.m
#implementation Secondvc
#synthesize delegate;
-(void)viewDidLoad{
//Do Something here!
}
//Pass Some Value When a button event occured in Second vc
-(IBAction)Send_Data{
[self dismissViewControllerAnimated:Yes completion:nil];
[self.delegate Object:[NSArray Arraywithobjects:#"Hello",nil]];
}
#end
//FirstVc.h
#import "Secondvc.h"
#interface FirstVc<Sendmessage>
#end
//FirstVc.m
#implementation FirstVc
-(void)viewDidLoad{
Secondvc* Object=[[Secondvc alloc]init];
Object.delegate=self;
}
#pragma mark Secondvc Deklegate method implementation
-(void)Object:(NSArray *)tosend{
NSLog(#"Recieved data Form Second VC Is:\n%#",tosend);
}
#end
HTH!Enjoy Coding.

Using the same view controller multiple times

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
}
}

Using different buttons to get to the same ViewController

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 :)

Programmatic creation of NSView in Cocoa

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];
}

Update UIViewController after Dismissing Modal Segue

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.

Resources