In my GameViewController class, I have this method displayWinScreen which gets the current level number and stores it in a public property in my WinViewController class called levelCompleted. It then runs this method called runPushAnimationWithController: which just pushes the WinViewController object onto the UINavigation stack.
- (void)displayWinScreen {
WinViewController *winViewController = [[WinViewController alloc] initWithNibName:#"WinViewController"
bundle:nil];
winViewController.levelCompleted = self.levelNumber;
[self runPushAnimationWithController:winViewController];
}
Then in WinViewController I set a button that, when pressed on the iPhone, calls this method:
-(IBAction)nextLevelSelection:(id)sender {
int num = [self.levelCompleted integerValue];
int newNum = num + 2;
self.levelCompleted = [NSNumber numberWithInteger:newNum];
GameViewController* nextLevelViewController = [[GameViewController alloc]
initWithNibName:#"GameViewController"
bundle:nil];
nextLevelViewController.levelNumber = self.levelCompleted;
[self runPushAnimationWithController:nextLevelViewController];
}
This method just increases that property value, and stores it in the GameViewController property levelNumber. When I put a breakpoint to see if that int number gets passed, everything checks out. If I originally played level 3, the number that eventually is nextLevelViewController.levelNumber is 4. Ignore the num + 2, the way I have it set up, the int value still will the increase the level by 1.
I use this same process of passing int properties for all my other UINavigationController methods and everything is ok. Except for this. Should I popping the controller or use another technique for view controller transitioning?
Here's the pop method I use to go back to the GameViewController from WinViewController.
-(void)runPopperAnimation:(UIViewController*)viewController {
CATransition *transition = [CATransition animation];
transition.duration = 0.30f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController popToViewController:viewController animated:NO];
}
how would I be able to pass that levelCompleted property from the
WinViewController back up to the GameViewController?
One way is to store it to a separate singleton class:
DataModel.h
#interface DataModel : NSObject
#property NSInteger *levelCompleted;
+ (id)sharedModel;
DataModel.m
#import "DataModel.h"
#implementation DataModel
#synthesize levelCompleted;
/* Return singleton model */
+ (id)sharedModel {
static DataModel *sharedModel = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedModel = [[self alloc] init];
});
return sharedModel;
}
- (id)init {
self = [super init];
if (self){
self.levelCompleted = 0;
return self;
}
You can then call this class from anywhere in your code and modify it's properties:
NSInteger levelCompleted = [DataModel sharedModel].levelCompleted;
To increment the value:
[DataModel sharedModel].levelCompleted+=1;
With this method, the levelCompleted will only persist over the lifecycle of the app execution. If you restart the device or close the app, the value is lost.
BUT
If you want to persist across restarts then you can save it to NSUserDefaults instead. This will persist of the lifetime of the app install:
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
To Retrieve the value:
NSNumber *levelCompleted =[standardUserDefaults objectForKey:#"levelCompleted"];
To Save the value:
[stadardUserDefaults setObject:levelCompleted forKey:#"levelCompleted"];
Related
I have a singleton class, and I have a property declared in it:
#property (nonatomic, strong) NSString *currentTableName;
+ (SuperNoteManager*)sharedInstance;
.m file:
+ (SuperNoteManager*)sharedInstance
{
static SuperNoteManager *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[SuperNoteManager alloc] init];
});
return _sharedInstance;
}
When I run my app for the first time, there is no data in the data base,so it shows the EmptyViewController.
#property (nonatomic, strong) SuperNoteManager *myManager;
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
_myManager=[SuperNoteManager sharedInstance];
}
-(void)changeRootView{
UIStoryboard *storyboard=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
HomeViewController *hVC=[storyboard instantiateViewControllerWithIdentifier:#"HomeViewController"];
UINavigationController *mNavVC=[storyboard instantiateViewControllerWithIdentifier:#"MainNavigationController"];
mNavVC.viewControllers=#[hVC];
[[UIApplication sharedApplication].keyWindow setRootViewController:mNavVC];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if ( [_myManager checkForDataInAllTables]) {
NSLog(#"All tables are empty");
}else{
//a note is saved, show home view controller
if (![_myManager isDatabaseEmpty]) {
[self changeRootView];
}
}
}
There is + button on NavigationBar on EmptyNotesViewController, and on tap '+',
NotesViewController is pushed from EmptyNotesViewController.
In the NotesViewController, after I write some notes, I save the notes in database:
NotesViewController:
#property (nonatomic,strong) SuperNoteManager *myManager;
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
_myManager.currentTableName=#"WorkTable";
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
NSLog(#"going back");
[self insertTextintoDatabase]; //Text is inserted . I double checked
}
}
And then When I go back to my EmpytNotesViewController, I check for data, and if data is present, I change the rootViewController as it is not EmptyNotesView anymore.
So When I go back to my EmptyNotesViewController:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if ( [_myManager checkForDataInAllTables]) {
NSLog(#"All tables are empty");
}else{
//a note is saved, show home view controller
//Put a breakpoint here
if (![_myManager isDatabaseEmpty]) {
[self changeRootView];
}
}
}
Here at the breakpoint _myManager.currentTableName is nil. why?
I set it in the NotesController, and it became nil when it come back to the EmptyNotesController.
I thought once a value is set in singleton, it will persist as long as the app is closed/killed.
Note: I have declared the property of my Singleton class as strong and also all the properties in the singleton are declared as strong.
It appears like you never get a reference to the SuperNoteManager singleton in NotesViewController, like you did in your EmptyNotesController.
Therefore the currentTableName property never gets set in the first place.
You want to insert:
_myManager = [SuperNoteManager sharedInstance];
in your -viewDidAppear: before you set the currentTableName property.
I have 2 controllers and i would like to send a float from LevelViewController to ViewController. But it always sends it as 0 (zero). Here is my LevelViewController.m
ViewController *inGame = [[ViewController alloc] init];
enemySpeedShouldBe = 0.800f - 0.07f * levelSelected;
inGame.enemySpeed = enemySpeedShouldBe;
Where levelSelected and enemySpeedShouldBe are also floats.
NSLog(#"%f", levelSelected) gives me the correct value (1.0, 2.0 etc.), but when I send it to ViewController, it's equal to 0.
Here is my ViewController.h
#property (nonatomic) float enemySpeed;
I've read about this issue but I couldn't find any way to make it work.
--/--/--/--/--/--/--/--/--/--/--EDIT--/--/--/--/--/--/--/--/--/--
here is my enemy millisecond counter methods in "ViewController.m"
-(void)enemyStartCounter{
enemyMs += 0.001;
if(enemyMs > enemySpeed){ // i use 'enemySpeed' nowhere except here
[enemyTimer invalidate];
[fire setHidden:YES];
[self enemyFired];
}
}
-(void)enemyCounter{
enemyMs = 0;
enemyTimer = [NSTimer scheduledTimerWithTimeInterval:0.001/1.0 target:self selector:#selector(enemyStartCounter) userInfo:nil repeats:YES];
}
and my button touchup inside action method in "LevelViewController.m"
- (void)didTapLevel:(UIButton *)buttonn{
levelSelected = (float)buttonn.tag + 1.0f ;
ViewController *inGame = [[ViewController alloc] init];
enemySpeedShouldBe = 0.800f - (0.07f * levelSelected);
inGame.enemySpeed = enemySpeedShouldBe;
[self performSegueWithIdentifier:#"1player" sender:self];
}
At some point in your code you are creating this instance of ViewController, configuring it with a value for enemySpeed, and then probably throwing it away.
Somewhere else, another instance is being created. This instance isn't configured, but you're calling enemyStartCounter on it, so it logs a zero.
You need to ensure that you understand what instances of each class you have and how they're being used. Your view controller could be created from a Storyboard segue, in this case you need to get a reference to it by intercepting the segue using prepareForSegue:sender:.
This code creates 2 different instances:
- (void)didTapLevel:(UIButton *)buttonn{
ViewController *inGame = [[ViewController alloc] init]; // create first instance
inGame.enemySpeed = enemySpeedShouldBe;
[self performSegueWithIdentifier:#"1player" sender:self]; // create second instance
}
So what you should be doing is:
- (void)didTapLevel:(UIButton *)buttonn{
[self performSegueWithIdentifier:#"1player" sender:self]; // create single instance
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
ViewController *inGame = (ViewController *)[segue destinationViewController]; // get single instance
inGame.enemySpeed = enemySpeedShouldBe;
}
I prefer to create a NSNumber property to do this:
#property (strong, nonatomic) NSNumber *enemySpeed; // in your viewController
// passing the parameter
inGame.enemySpeed = [NSNumber numberWithFloat:(0.800f - 0.07f * levelSelected)];
Then if you pass the property between the controllers you can do this :
NSLog(#"%f", [enemySpeed floatValue]);
I've a problem with an array.
There's an array with some objects of the class Car in my CarViewController:
...Car.h
#interface Car : NSObject
#property (nonatomic, strong) NSString *name;
..and Car.m
- (void)encodeWithCoder:(NSCoder *)coder;
{
[coder encodeObject:name forKey:#"name"];
}
- (id)initWithCoder:(NSCoder *)coder;
{
self = [[Car alloc] init];
if (self != nil)
{
name = [coder decodeObjectForKey:#"name"];
}
return self;
}
and CarViewController
Car *car1 = [Car new];
car1.name = #"A1";
...
cars = [NSArray arrayWithObjects: car1, car2, ..., nil];
But when I now try to have access to this array in NewViewController there is a problem:
- (IBAction)btn:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
CarViewController *vc = (CarViewController *)[storyboard instantiateViewControllerWithIdentifier:#"CarVC"];
Car *car = [vc.cars objectAtIndex:0];
NSLog(#"%#", car.name);
}
But in the log is just written that car.name = (null).
Thanks in advance to your effort.
UPDATE:
- (IBAction)btn:(id)sender {
UINavigationController *nav = self.tabBarController.viewControllers[1];
CarViewController *vc = nav.topViewController;
Car *car = [vc.cars objectAtIndex:0];
NSLog(#"%#", car.name);
}
I've tried something (thanks to rdelmar for his effort), but the result is still the same.
if you put this code
Car *car1 = [Car new];
car1.name = #"A1";
...
cars = [NSArray arrayWithObjects: car1, car2, ..., nil];
in the viewDidLoad method of your view controller, the the result is normal because your view is not loaded yet. (the viewDidLoad method has not been called yet)
I'm not familiar with storyboards but I think when you call
CarViewController *vc = (CarViewController *)[storyboard instantiateViewControllerWithIdentifier:#"CarVC"];
it will instantiate a new ViewController. So if you try to access cars property it will be nil if this new viewController is not presented on screen
Apple Documentation
You use this method to create view controller objects that
you want to manipulate and present programmatically in your
application. Before you can use this method to retrieve a view
controller, you must explicitly tag it with an appropriate identifier
string in Interface Builder.
This method creates a new instance of the specified view controller
each time you call it.
Here's the property declaration in my SlidingVC header, a subclass of UITableViewController:
#property (strong, nonatomic) NSString *user;
Here's my synthesize line:
#synthesize user = _user,sortedWordlist = _sortedWordlist, wordlist = _wordlist;
Here's my custom init:
- (id)initWithUser:(NSString*)theUser
{
self = [super init];
if (self) {
if ([theUser isEqualToString:#"user"]) {
_user = #"user";
}
else if ([theUser isEqualToString:#"opponent"]){
_user = #"opponent";
}
}
return self;
}
So what's happening is that as I step pver initWithUser:, I see that _user takes on the memory address of theUser in the variable debugger window. I step over return self and then to the closing } of the method and it's still set. However, Xcode returns to return self one more time and then if I step over that _user doesn't have a memory address next to it anymore, and it remains null for the methods that follow.
Why is it returning twice and then setting to null the second time?
Here's the method in my MainVC that instantiates the SlidingVC:
- (WSWordlistTableViewController *)setupWordlistTableViewController:(NSString*)user
{
WSWordlistTableViewController *wordlistTableViewController;
UIView *wordlistContainerView;
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle: nil];
if ([user isEqualToString:#"user"]){
if(!self.userWordlistTableViewController){
self.userWordlistTableViewController = [[WSWordlistTableViewController alloc] initWithUser:#"user"];
wordlistTableViewController = self.userWordlistTableViewController;
wordlistContainerView = self.userWordlistContainerView;
}
}
else if ([user isEqualToString:#"opponent"]) {
if(!self.opponentWordlistTableViewController){
self.opponentWordlistTableViewController = [[WSWordlistTableViewController alloc] initWithUser:#"opponent"];
wordlistTableViewController = self.opponentWordlistTableViewController;
wordlistContainerView = self.opponentWordlistContainerView;
}
}
wordlistTableViewController = [mainStoryboard instantiateViewControllerWithIdentifier:#"wordlistTableViewController"];
wordlistTableViewController.view.frame = wordlistContainerView.bounds;
wordlistTableViewController.view.autoresizingMask = wordlistContainerView.autoresizingMask;
[self addChildViewController:wordlistTableViewController];
[wordlistContainerView addSubview:wordlistTableViewController.view];
[wordlistContainerView bringSubviewToFront:wordlistTableViewController.wordlistTableView];
[wordlistTableViewController didMoveToParentViewController:self];
return wordlistTableViewController;
}
And the method that calls that, depending on which button is pressed:
- (IBAction)slideUserWordlistView:(id)sender {
if(!self.userWordlistTableViewController){
[self setupWordlistTableViewController:#"user"];
}
// (sliding drawer code here)
}
Or:
- (IBAction)slideOpponentWordlistView:(id)sender {
if(!self.opponentWordlistTableViewController){
[self setupWordlistTableViewController:#"opponent"];
}
// (sliding drawer code here)
}
What I'm doing is sliding out a view that contains my SlidingVC. I have two of them, one for each of two users. When each respective button is pressed I check if each respective SlidingVC exists, if not, instantiate then add to the the slidingViewContainer.
I have got a UITabBarController in a Storyboard. Right now, it has got 5 UITabBarItems. When I am in the other UITabBarItem, I want to update the Badge on the other UITabBarItem(my "Downloads") just like the iTunes App does with this "jump-like" animation when you buy a song or album. Is this possible? If Yes, how?
Thank you.
Yes...
There is a lot to an animation like the I'll call it "send to Downloads" type animation. I'll answer this question using an example.
Warning: this example breaks the MVC paradigm more than I'd like, but it's long enough as it is.
I'll use a simple Storyboard like this (in fact, exactly this):
I'll start by describing the "First View Controller - First":
Those many buttons in the view are connected to the one listed IBAction method. And that's about all the description needed for that view controller. Here is its .m file:(truncated)
//#import "First_View_Controller.h"
#interface First_View_Controller ()
#property (weak, nonatomic) DownloadViewController *downloadViewController;
#end
#implementation First_View_Controller
#synthesize downloadViewController = _downloadViewController;
-(DownloadViewController *)downloadViewController{
if (!_downloadViewController){
// Code to find instance of DownloadViewController in the tabBarController's view controllers.
for (UIViewController *vc in self.tabBarController.viewControllers) {
if ([vc isKindOfClass:[DownloadViewController class]]){
_downloadViewController = (DownloadViewController *)vc;
break;
}
}
}
return _downloadViewController;
}
-(IBAction)buttonPush:(UIButton *)button{
[self.downloadViewController addADownload:nil withViewToAnimate:button];
}
// Other typical VC crap...
#end
The IBAction is fairly self-explanatory. It gets reference to the instance of DownloadViewController, by looking through the tabBarController's view controllers, and passes the view to animate to that instance.
Now for DownloadViewController.m. It's a lot of code. I've commented it, to try to make it clear:
#import "DownloadViewController.h"
#import <QuartzCore/QuartzCore.h>
// A Category on UITabBar to grab the view of a tab by index.
#implementation UITabBar (WhyIsntThisBuiltIn)
-(UIView *)nj_ViewOfTabNumber:(NSUInteger)number{
if (number == NSNotFound) return nil;
// Fairly standard method for getting tabs, getting the UIControl objects from the 'subviews' array.
// I pulled the next few lines from an SO question.
NSMutableArray *tabs = [[NSMutableArray alloc] init];
[self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
if ([(NSObject *)obj isKindOfClass:UIControl.class]){
[tabs addObject:obj];
}
}];
// The code above gets the tabs' views, but they may not be in the correct order.
// This sort is required if a view controller has been replaced,...
// Since, in that case, the order in which the tabs' views appear in the 'subviews' array will not be the left-to-right order.
[tabs sortUsingComparator:^NSComparisonResult(UIView *obj1, UIView *obj2){
CGFloat v1 = obj1.center.x;
CGFloat v2 = obj2.center.x;
if (v1<v2) return NSOrderedAscending;
if (v1>v2) return NSOrderedDescending;
return NSOrderedSame;
}];
// This if is required for the case where the view controller is in the "more" tab.
if (number >= tabs.count) number = tabs.count-1;
return [tabs objectAtIndex:number];
}
#end
// A Category on UITabBarController to get the view of a tab that represents a certain view controller.
#implementation UITabBarController (WhyIsntThisBuiltIn)
-(UIView *)nj_viewOfTabForViewController:(UIViewController *)viewController{
// Find index of the passed in viewController.
NSUInteger indexOfViewController = [self.viewControllers indexOfObject:viewController];
if (indexOfViewController == NSNotFound) return nil;
// Return the view of the tab representing the passed in viewController.
return [self.tabBar nj_ViewOfTabNumber:indexOfViewController];
}
#end
// Insert required warning about using #defines here.
#define MY_ANIMATION_DURATION 0.8
#implementation DownloadViewController{
NSUInteger _numberOfDownloads;
}
-(void)updateBadgeValue{
self.tabBarItem.badgeValue = [NSString stringWithFormat:#"%i",_numberOfDownloads];
}
// This method creates a "snapshot" of the animation view and animates it to the "downloads" tab.
// Removal of the original animationView must, if desired, be done manually by the caller.
-(void)addADownload:(id)someDownload withViewToAnimate:(UIView *)animationView{
// update model...
_numberOfDownloads++;
// Animate if required
if (animationView){
// Create a `UIImageView` of the "animationView" name it `dummyImageView`
UIGraphicsBeginImageContextWithOptions(animationView.frame.size, NO, [[UIScreen mainScreen] scale]);
[animationView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *dummyImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *dummyImageView = [[UIImageView alloc] initWithImage:dummyImage];
dummyImageView.frame = animationView.frame;
// Determine UIView of tab using non-private API.
UITabBarController *tabBarController = self.tabBarController;
UIView *downloadsTab = [tabBarController nj_viewOfTabForViewController:self];
// Determine animation points in tabBarController's view's coordinates.
CGPoint animationStartPoint = [tabBarController.view convertPoint:dummyImageView.center fromView:dummyImageView.superview];
CGPoint animationEndPoint = [tabBarController.view convertPoint:downloadsTab.center fromView:downloadsTab.superview];
CGFloat totalXTravel = animationEndPoint.x - animationStartPoint.x;
// This is an arbitrary equation to create a control point, this is by no means canonical.
CGPoint controlPoint = CGPointMake(animationEndPoint.x, animationStartPoint.y - fabs(totalXTravel/1.2));
// Create the animation path.
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:animationStartPoint];
[path addQuadCurveToPoint:animationEndPoint controlPoint:controlPoint];
// Create the CAAnimation.
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
moveAnimation.duration = MY_ANIMATION_DURATION;
moveAnimation.path = path.CGPath;
moveAnimation.removedOnCompletion = NO;
moveAnimation.fillMode = kCAFillModeBoth;
[tabBarController.view addSubview:dummyImageView];
dummyImageView.center = animationStartPoint;
// Animate the move.
[dummyImageView.layer addAnimation:moveAnimation forKey:#""];
// Use the block based API to add size reduction and handle completion.
[UIView animateWithDuration:MY_ANIMATION_DURATION
animations:^{
dummyImageView.transform = CGAffineTransformMakeScale(0.3, 0.3);
}
completion:^(BOOL b){
// Animate BIG FINISH! nah, just...
[dummyImageView removeFromSuperview];
[self updateBadgeValue];
}];
}
}
// Other typical VC crap...
#end
And that's about it. When run, this code produces a fairly strong jump from the buttons on the top left, but the buttons on the right, especially on the lower right, are sort-of tossed. And as the animation ends the badge on the downloads tab counts up. A pretty decent knock-off of the effect Apple uses when you purchase content on iTunes.
Remember to add the Quartz Framework to your app.