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.
Related
Here is my .h file
#import <UIKit/UIKit.h>
#interface PersonViewController : UIViewController
#property(strong,nonatomic) NSString *personTitle;
And here is my .m file
#interface PersonViewController ()
#property (weak, nonatomic) IBOutlet UILabel *titleView;
#end
#implementation PersonViewController
//stuff …
-(void)setPersonTitle:(NSString *)personTitle
{
[self.titleView setText:personTitle];// also self.titleView.text=personTitle
[self.titleView setNeedsDisplay];
NSLog(#"The title shoud match as %# :: %#",personTitle,self.titleView.text);
}
-(NSString *)personTitle
{
return self.titleView.text;
}
//… more stuff
#end
The logging shows that the value is (null) for self.titleView.text whereas personTitle prints the appropriate value.
I remember doing this same thing a number of times and it worked. Any ideas why it’s failing this time?
update I use storyboard to set my scenes. And I am using xcode-5 and iOS-7
update: how I call
The user clicks a button, leading to a push segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"enter prepare for segue.");
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
if ([segue.identifier isEqualToString:the_identifier_for_person]) {
NSLog(#"segue to person is progressing“);
if ([segue.destinationViewController isKindOfClass:[PersonViewController class]]) {
NSLog(#"segue to person destination is a match");
PersonViewController *aPerson = (PersonViewController *)segue.destinationViewController;
aPerson.personTitle=((MyItem*)self.allItems[indexPath.row]).title;
NSLog(#"segue to person is done");
}
}
}
This sounds like you forgot to wire up your UILabel in the storyboard. Can you confirm that self.titleView is not null?
View controllers create their views on demand, but can spot that only via a call to view. When the view is loaded, your outlets will be populated.
Either call view to force loading or keep the string in abeyance until you get viewDidLoad.
(aside: prior to iOS 6, views would also be released in low-memory situations so the idiomatic thing is to store the string and populate on viewDidLoad)
Having accepted another answer, I wanted to show the pattern that I actually used to solve the problem, in case someone else comes looking. This pattern is best practice (yes, I forgot it for a long moment there).
#pragma mark - update UI
-(void)setPersonTitle:(NSString *)personTitle
{
_personTitle=personTitle;
if (self.view.window) [self updateUI];//only if I am on screen; or defer to viewWillAppear
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateUI];
}
-(void)updateUI
{
self.titleView.text=self.personTitle;
}
It is always important to update the ui when the data has changed, which is why I must make the call inside setPersonTitle. But because the IBOutlets are not yet set when I set personTitle in prepareForSegue, then I must also make the call inside viewWillAppear.
Do you actually call the -(void)setPersonTitle:(NSString *)personTitle method?
It seems that you aren't calling it correctly which would result in the title being null.
After reviewing the prepareForSeque it is clear that you are not calling the method. You are actually just changing the #property named personTitle.
In the viewDidLoad you should have it so that self.titleView.text = self.personTitle;
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'm trying to use storyboard and get things working properly. I've added a a Container View to one of my existing views. When I try to add a reference to this in my view controller .h file (ctrl-drag), I get a IBOutlet UIView *containerView. How do I get a reference to the container view's view controller instead? I need the container view controller so I can set it's delegate to my view's controller so they can "talk" to each other.
I have my story board setup as:
And its referenced in my .h file as:
Notice in the .h that is is a UIView, not my InstallViewController for the view. How do I add a reference to the view controller? I need to be able to set its delegate.
There is another solution by specifying an identifier for the embed segue(s) and retrieve the corresponding view controllers in method prepareForSegue:
The advantage of this way is that you needn't rely on a specific order in which your child view controllers are added due to the fact that each child view controller is embedded via an unique segue identifier.
Update 2013-01-17 - Example
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
// -- Master View Controller
if ([segue.identifier isEqualToString:c_SegueIdEmbedMasterVC])
{
self.masterViewController = segue.destinationViewController;
// ...
}
// -- Detail View Controller
else if ([segue.identifier isEqualToString:c_SegueIdEmbedDetailVC])
{
self.detailViewController = segue.destinationViewController;
// ...
}
}
c_SegueIdEmbedMasterVC & c_SegueIdEmbedDetailVC are constants with the corresponding ID of the segue IDs defined in the storyboard.
When you add a container view the xcode calls the UIViewController method addChildViewController:
In your case, you can get the container ViewController looking for it on the SplashViewController's list of childViewControllers, something like this:
for (UIViewController *childViewController in [self childViewControllers])
{
if ([childViewController isKindOfClass:[InstallViewController class]])
{
//found container view controller
InstallViewController *installViewController = (InstallViewController *)childViewController;
//do something with your container view viewcontroller
break;
}
}
I had the same doubt yesterday :)
The answer of Vitor Franchi is correct but could be more performant and convenient. Especially when accessing the child view controller several times.
Create a readonly property
#interface MyViewController ()
#property (nonatomic, weak, readonly) InstallViewController *cachedInstallViewController;
#end
Then create a convenient getter method
- (InstallViewController *)installViewController
{
if (_cachedInstallViewController) return _cachedInstallViewController;
__block InstallViewController *blockInstallViewController = nil;
NSArray *childViewControllers = self.childViewControllers;
[childViewControllers enumerateObjectsUsingBlock:^(id childViewController, NSUInteger idx, BOOL *stop) {
if ([childViewController isMemberOfClass:InstallViewController.class])
{
blockInstallViewController = childViewController;
*stop = YES;
}
}];
_cachedInstallViewController = blockInstallViewController;
return _cachedInstallViewController;
}
From now on access the child view controller that way
[self.installViewController doSomething];
UIView* viewInsideOfContainer = installerView.subviews[0];
Will give you the UIView inside of the UIViewController that your controller UIView references. You can cast the subview to any type that inherits from UIView.
If the nib is loaded it will call addChildViewController as part of the initialisation process
so a performant solution could be also to overwrite
- (void)addChildViewController:(UIViewController *)childController
there you can catch your childController e.g. by comparing its Class and assign it to a property / ivar
-(void)addChildViewController:(UIViewController *)childController
{
[super addChildViewController:childController];
if([childController isKindOfClass:[InstallViewController class]])
{
self.installViewController = (InstallViewController *)childController;
}
}
This will save your from iterating trough the childViewControllers.
I have a main view with 3 buttons. Clicking on any of the buttons adds a SubView.
The buttons have different titles and are all linked to IBAction "switchView"
The "switchView" code is below.
- (IBAction)switchView:(id)sender{
secondView *myViewController = [[secondView alloc] initWithNibName:#"secondView" bundle:nil];
[self.view addSubview:myViewController.view];
}
The "secondView" loads up correctly and everything works well.
The problem is I want to be able to know which button was the Sender.
I don't want to create 3 subviews, one for each button. The code and XIB would be absolutely the same>
The only difference would be a variable that I would like to set up in the second view (viewDidLoad method) depending on who is the Sender (which button was clicked)
Is this possible? Or I would need to create 3 subViews - one for each button?
Your help is greatly appreciated!
You can identify different buttons with the tag property.
e.g. with your method:
-(IBAction)switchView:(id)sender {
UIButton *button = (UIButton*)sender;
if (button.tag == 1) {
//TODO: Code here...
} else if (button.tag == 2) {
//TODO: Code here...
} else {
//TODO: Code here...
}
}
The tag property can be set via the InterfaceBuilder.
Hope this helps.
I think you can solve in 2 ways:
Create a property like:
#property (nonatomic, strong) IBOutlet UIButton *button1, *button2, *button3;
in your viewcontroller and link the buttons to them as referencing outlet on the XIB.
Give a different tag to each button on your xib and ask for the tag of the sender with UIButton *b=(UIButton*)sender; b.tag; like Markus posted in detail.
Solving my problem it all came down to transferring data between the mainView and subView.
In my mainView.h I declared an NSString and its #property
...
NSString *btnPressed;
}
#property(nonatomic, retain) NSString *btnPressed;
...
then in my mainView.m inside the switchView method I did this:
- (IBAction)switchView:(id)sender{
secondView *myViewController = [[secondView alloc] initWithNibName:#"secondView" bundle:nil];
btnPressed = [NSString stringWithFormat:#"%i", [sender tag]];
[myViewController setBtnPressed:self.btnPressed];
[self.view addSubview:myViewController.view];
}
This line in the code above actually takes care of transferring the data to the newly created subView:
[myViewController setBtnPressed:self.btnPressed];
Then in my secondView.h I declare exactly the same NSString *btnPressed and its #property (though this a completely different object than the one declared in main)
Then in my secondView.m I get the value of the button pressed I'm interested in.
- (void)viewDidLoad {
[super viewDidLoad];
int theValueOfTheButtonPressed = [self.btnPressed intValue];
}
This works well.
Don't forget to #synthesize btnPressed; as well as [btnPressed release]; in both mainView.m and secondView.m
I tried a few things myself, but couldnt really get the handle around it.
I wanna do two things:
First the user can press one of three buttons - They all link to the same ViewController, but when User Presses the first button three labels change accordingly in this second ViewController. And then the user can enter some data which will be displayed in the third view, also accordingly on which button was pressed in the first view.
I tried it with IF Statements, e.g. (IF ViewController.button1waspressed == True) and it didnt really work. I also tried it with tags e.g. (Button1.tag = 1)
Could someone give me a short example on how this could work?
FirstViewController.m
- (IBAction)switch:(id)sender;
{
SecondViewController *second =[[SecondViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:second animated:YES];
SecondViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
if (sender == self.button1) {
NSString *greeting = [[NSString alloc] initWithFormat:#"Randomtext"];
self.label.text = greeting;
}
}
The problem is obvious in this one, SecondViewController cant see the property from the first one. (And yes I imported the FirstViewController and vice versa)
Your buttons should all directly call IBActions (methods defined like so):
- (IBAction)doSomething:(id)sender;
Defining them as IBActions exposes them to be connected with the blue connection lines in interface builder. Once you've hooked them up and the method is being called, you can simply use an equality check on the sender parameter, which the calling button will automatically set as itself.
if (sender == self.myButton) {
// do something
}
Here I'm assuming that you've got a property called myButton in your ViewController, which would be an IBOutlet:
#property (nonatomic, retain) IBoutlet UIButton *myButton;
This exposes that property to be connected with the blue connection lines in interface builder, so your ViewController will know exactly which button you're talking about when you say myButton.