probably an easy question...I'm a beginner programmer, so bear with me :)
I've made my first 'real' iPhone app that draws cards from a deck until a Joker is drawn, then the game is over.
So far, so good and it all works, except I have to use a work-around which I really hate.
My Deck object is only available where I declare it, which seems normal but yet irritating.
Basically, I have to make an entire new deck every time the user hits the "Draw Card" button...
- (IBAction)drawCard:(id)sender {
Deck *deck = [[Deck alloc]init];
deck.generate;
unsigned int randomIndex = arc4random_uniform(deck.deckArray.count);
Card *topCard = [deck.deckArray objectAtIndex:randomIndex - 1];
if (topCard.value == 11) {
NSString *cardInfo = [NSString stringWithFormat:#"You drew a Jack of %#", topCard.suit];
self.cardDrawn.text = cardInfo;
}else if (topCard.value == 12) {
NSString *cardInfo = [NSString stringWithFormat:#"You drew a Queen of %#", topCard.suit];
self.cardDrawn.text = cardInfo;
}else if (topCard.value == 13) {
NSString *cardInfo = [NSString stringWithFormat:#"You drew a King of %#", topCard.suit];
self.cardDrawn.text = cardInfo;
}else {
NSString *cardInfo = [NSString stringWithFormat:#"You drew a %d of %#", topCard.value, topCard.suit];
self.cardDrawn.text = cardInfo;
}
[deck removeCard];
if (topCard.value == 14) {
self.cardDrawn.text = #"You drew the Joker Bomb!";
//Destroy deck
deck = nil;
[drawCard setEnabled:NO];
[playAgain setEnabled:YES];
[playAgain setHidden:NO];
}
}
- (IBAction)playAgain:(id)sender {
self.cardDrawn.text = nil;
[drawCard setEnabled:YES];
[playAgain setEnabled:NO];
[playAgain setHidden:YES];
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self =[super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
Deck *deck = [[Deck alloc]init];
deck.generate;
}
return self;
}
#end
If I take away Deck *deck = [[Deck alloc]init]; in my drawCard button method, I get a bunch of errors and the app won't build. I've tried #synthesizeing the deck object, but that doesn't seem to work either, and I also tried making a pointer to deck but that was kind of useless and did not work as well.
I wanted to implement a label that would show how many cards are left in the deck, but that would be impossible since a new deck is being generated every time the user taps drawCard.
Sorry if this is a stupid question & thanks!
#interface ViewControllerTheOPDidntProvideANameFor ()
#property (nonatomic, strong) Deck *deck;
#end
#implementation ViewControllerTheOPDidntProvideANameFor
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self =[super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
self.deck = [[Deck alloc]init];
[self.deck generate];
return self;
}
#end
There are some problems in your code. First of all what does "deck.generate;" do? That's not a valid Objective-C syntax.
The other thing is that you're realocating a deck everytime the drawCard method is called. Instead of using:
Deck *deck = [[Deck alloc]init];
You could declare Deck as an instance variable inside your interface and change the line above with:
deck = [Deck new];
In your .h file, declare something like this:
#property (nonatomic, strong) Deck *deck;
Then, in your .m file, initialize the deck (most likely in viewDidLoad or viewWillAppear) using something like this:
self.deck = [[Deck alloc] init];
Then, you'll be able to use the same deck property using self.deck like this:
[self.deck generate]; //Good practice to use brackets instead of dot notation for function calls in Objective-C since dot operators indicate properties
Hope this helps!
EDIT:
Per DarkDust's remark, you can use an instance variable as well:
//.h
#interface YourViewControllerName : UIViewController {
Deck *deck;
}
//.m
- (void)viewDidLoad
{
[super viewDidLoad];
//Initialize the deck
deck = [[Deck alloc] init];
}
Your deck will then be accessible using deck.
Related
For some reason I am experiencing an issue where, inside of a viewController's code (which runs well after the viewDidLoad method is called), self always returns nil. (Running on iPad Air with iOS 9.2.1, app built with 9.2 as the target).
In the initialization code we have a run-of-the-mill initter:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
};
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) { }
return self;
}
The viewController gets instantiated like this:
myVC * myVC = [[myVC alloc] initWithNibName:nil bundle:nil];
The names of the myVC class and .xib file are the same.
There's a property called:
#property (weak, nonatomic) IBOutlet UILabel *ipAddressLabel;
In a method inside myVC, I have a method like this:
- (void) networkPropertiesDidChange:(NSDictionary *)properties {
void (^completionBlock)(UIAlertAction *) = ^(UIAlertAction *action) {
[self.serialNumberField setEnabled:false];
};
if([properties[#"result" isEqualToString #"error"]) {
[Utility showBasicAlertControllerWithText:#"Device not registered." completionBlock:completionBlock sender:self];
return;
}
else if ([properties[#"result"] isEqualToString #"ipChange"]) {
NSString *newAddy = [[NSString alloc] initWithString:properties[#"ipAddress"]];
self.ipAddressLabel.text = newAddy;
return;
}
else return;
}
However the address label just goes blank.
When I step through the code while it executes, on the line where I initialize newAddy, self is not nil, and ipAddressLabel is not nil. However when the runtime hits self.ipAddressLabel.text, then it goes through initWithNibName, and returns nil for self! Then ipAddressLabel gets set to nil, and then it turns blank on the view.
At this point myVC has already successfully loaded and is on the screen. So I cannot understand why self is returning nil for self, inside this method... it's very odd.
If I delete my override of the initWithNibName method, then everything just works perfectly. If I check if (self != nil) at the beginning of initWithNib name before setting self=[super... ], then the view draws about 1" too low on the screen and gets cut off.
Just trying to understand why this was happening. Thanks.
The problem is here:
void (^completionBlock)(UIAlertAction *) = ^(UIAlertAction *action) {
[self.serialNumberField setEnabled:false];
};
It needs to be weakSelf...
__weak typeof(self) weakSelf = self;
void (^completionBlock)(UIAlertAction *) = ^(UIAlertAction *action) {
[weakSelf.paxSerialNumberField setEnabled:false];
};
Don't ask me why, my brain exploded.
The object of this application is to translate between english and spanish words.
(Checks text input against all array values to see if it's there and then compares that index to the second array, and displays the parallel value).
That part is working. If the word entered does not exist in the array, I am supposed to have a message like 'No translation available' display in the label. My problem is, I can either get the message to display for nothing or everything - rather than just when it is supposed to.
#import "TranslatorViewController.h"
#interface TranslatorViewController ()
#property (weak, nonatomic) IBOutlet UITextField *textField;
#property (weak, nonatomic) IBOutlet UILabel *label;
- (IBAction)translate:(id)sender;
#property (nonatomic, copy) NSArray *english;
#property (nonatomic, copy) NSArray *spanish;
#end
#implementation TranslatorViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_textField.delegate = self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//make the keyboard go away
-(BOOL) textFieldShouldReturn:(UITextField *)textField {
{[textField resignFirstResponder];}
return YES;
}
- (instancetype)initWithCoder:(NSCoder*)aDecoder { self = [super initWithCoder:aDecoder]; if(self) { // Add your custom init code here
self.english = #[#"phone",
#"dog",
#"sad",
#"happy",
#"crocodile"];
self.spanish = #[#"telefono",
#"perro",
#"triste",
#"felize",
#"cocodrilo"];
} return self; }
- (IBAction)translate:(id)sender {
//loop through the array
NSString *englishWord = self.textField.text;
for (int index=0; index<[self.english count]; index++)
if([[self.english objectAtIndex:index]
isEqualToString:englishWord])
//retrieve the accompanying spanish word if english word exists in the array
{NSString *spanishWord = [self.spanish objectAtIndex:index];
//and display it in the label
self.label.text = spanishWord;}
//Need code to display 'no translation' if word was not found.
}
#end
The simplest way to do this is probably to set the label's text field to "No translation" before entering the loop. If no match is found, the label will never be re-set to anything else.
There are lots of other ways to structure logic to give you this result. I might tighten up that last loop of code by doing this instead:
NSString * englishWord = self.textField.text;
NSUInteger spanishWordIndex = [self.english indexOfObject:englishWord];
if (spanishWordIndex == NSNotFound) {
self.label.text = #"No translation";
} else {
self.label.text = self.spanish[spanishWordIndex];
}
Why not use an NSDictionary?
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
self = [super initWithCoder:aDecoder];
if(self) { // Add your custom init code here
self.translationDict = #{#"phone":#"telefono",
#"dog":#"perro",
#"sad": #"triste",
#"happy": #"felize",
#"crocodile": #"cocodrilo"]; // self.translationDict is an NSDictionary
}
return self;
}
- (IBAction)translate:(id)sender {
NSString *englishWord = self.textField.text;
NSString *spanishWord=self.translationDict[englishWord];
if (spanishWord == nil) {
spanishWord="No Translation";
}
self.label.text=spanishWord;
}
I put
self.label.text = #"No translation available";
before the if statement which is I believe what Ben Zotto was trying to say! I just wasn't sure how to actually do it at first.
I'm a newb with this stuff.
But that did what I needed it to.
Thanks all!
Reformatted the answer to include your entire code. You should be able to just copy paste into your code.
- (IBAction)translate:(id)sender {
NSString *englishWord = self.textField.text;
NSString *spanishWord = #"No translation found.";
for (int index=0; index<[self.english count]; index++)
{
if([[self.english objectAtIndex:index] isEqualToString:englishWord])
{
//retrieve the accompanying spanish word if english word exists in the array
spanishWord = [self.spanish objectAtIndex:index];
}
}
self.label.text = spanishWord;
}
I am working on a simple app to track my daughter's hockey games. My problem is that when I stop and restart the app some of the data doesn't get loaded back in. (I'm checking with a log statement that just shows zero regardless of what was there before.)I'm not sure if the problem is in the loading or the saving.
(Sorry for the long post, but I'm not sure where to look.)
It uses a Games class that looks like this:
#import <Foundation/Foundation.h>
#interface Game : NSObject <NSCoding>//conforms to this protocol so data can be prepared for read/write to file
//used primarily in GameFactsViewController
#property (nonatomic, strong) NSString *opponent;
#property (nonatomic, strong) NSDate *dateOfGame;
//used primarily in PlayerActionsViewController
#property (nonatomic) NSInteger shotsOnGoal;
#property (nonatomic) NSInteger shotsNotOnGoal;
#property (nonatomic) NSInteger passesCompleted;
#property (nonatomic) NSInteger passesNotCompleted;
#property (nonatomic) NSInteger takeaways;
#property (nonatomic) NSInteger giveaways;
#property (nonatomic) NSInteger faceoffsWon;
#property (nonatomic) NSInteger faceoffsLost;
#property (nonatomic) NSInteger shifts;
#property (nonatomic) NSInteger blockedShots;
#end
My problem is that the opponent and dateOfGame properties and getting saved and loaded when I start the app again, but none of the other properties are.
Tha main controller is a tableview controler with each game as a row.The opponent and dateOfGame properties are set in a tableview controller and the others in a view controller inside a tab bar controllers. The seques to these controllers are made from a disclosure indicator for the first and by clicking on the row for the second. These work fine, using this code:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//check for proper seque
if ([segue.identifier isEqualToString:#"AddGame"]) {
//identify the top level view controller holding the GameFactsVC (see storyboard)
UINavigationController *navigationController = segue.destinationViewController;
//go down one level to get the GFVC
GameFactsViewController *controller = (GameFactsViewController *) navigationController.topViewController;
//set the current controller (the HSVC) as the delegate for the GFVC
controller.delegate = self;
} else if ([segue.identifier isEqualToString:#"EditGame"]) {
//as above to get to the right place
UINavigationController *navigationController = segue.destinationViewController;
GameFactsViewController *controller = (GameFactsViewController *) navigationController.topViewController;
controller.delegate = self;
//"sender" here is what was clicked, the detail disclosure icon
//this identifies what game data to load to edit
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
controller.gameToEdit = _games[indexPath.row];
} else if ([segue.identifier isEqualToString:#"GameDetails"]) {
UITabBarController *tabBarController = segue.destinationViewController;
PlayerActionsViewController *controller = (PlayerActionsViewController *)[[tabBarController viewControllers] objectAtIndex:0];
controller.delegate = self;
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
controller.gameToEditPerformance = _games[indexPath.row];
NSLog(#"Data passed %ld", (long)controller.gameToEditPerformance.shotsOnGoal);
}
}
I am returning to the main controller with this method for the opponent and dateOfGame and I can log the data coming back.
-(IBAction) done
{
//Use delegate to capture entered data and pass to HSVC
//methods here are delegate methods listed above and defined in the HSVC
//see if the data was empty when view loaded
if (self.gameToEdit == nil) {
Game *game = [[Game alloc] init];
game.opponent = self.opponentField.text;
game.dateOfGame = [self convertToDate:self.dateLabel.text withFormat:#"MMM d, yyyy"];
[self.delegate gameFactsViewController:self didFinishAddingGame:game];
} else {
self.gameToEdit.opponent = self.opponentField.text;//updates data model; will update display in delegate method
self.gameToEdit.dateOfGame = [self convertToDate:self.dateLabel.text withFormat:#"MMM d, yyyy"];
[self.delegate gameFactsViewController: self didFinishEditingGame:self.gameToEdit];
}
}
Similarly, I am passing the rest of the data back from the other controller with this line at the end of a long method that sets the data values and I can log the correct data coming back here, too.
[self.delegate playerActionsViewController:self didEditGameData:self.gameToEditPerformance];
I'm calling the Save method with these two similar methods. (The key difference as I see it as I have updated the display in the other table already, while I still need to update the main view.)
- (void) gameFactsViewController:(GameFactsViewController *)controller didFinishEditingGame:(Game *)game
{
//edit already made in data model in GFVC
//need to update display
NSInteger index = [_games indexOfObject: game];//locate the item being edited in the games array
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];//get the right cell
[self configureDataForCell:cell withGame:game];//puts the game data into the labels in the cell
[self saveGames];//write the current data to the file
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void) playerActionsViewController: (PlayerActionsViewController *) controller didEditGameData: (Game *) game
{
NSLog(#"Paased back shots %ld", (long) game.shotsOnGoal);
[self saveGames];
}
Now for data saving and loading. I'm using this code:
- (void) loadGames
{
NSString *path = [self dataFilePath];//for convenience below
//if there is already a data file, unarchive/decode and load games array
//else create an empty arry to hold games
if ([[NSFileManager defaultManager] fileExistsAtPath: path]) {
NSData *data = [[NSData alloc] initWithContentsOfFile: path];//data structure created and loaded with file data
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData: data];//archiver created and connected to data
_games = [unarchiver decodeObjectForKey:#"Games"];
[unarchiver finishDecoding];//data now in games array
} else {
_games = [[NSMutableArray alloc] initWithCapacity:50];
}
}
- (void) saveGames
{
NSMutableData *data = [[NSMutableData alloc] init];//data structure to hold the data to be saved after encoding
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:_games forKey:#"Games"];//I believe key here needs to match class name that will be saved. It tells archiver how to encode object properly
[archiver finishEncoding];//finish encoding, with data now in data structure
[data writeToFile:[self dataFilePath] atomically:YES];//write data structure to file determined above
}
Somewhere here there is a difference in the _games array, I guess, between the two situations, but I'm not seeing it. Or it's some other problem.
Thanks.
The problem was in my Games class. I forgot to include the keys to encode and decode the NSIntegers using
[aCoder encodeInteger:self.shotsOnGoal forKey:#"ShotsOnGoal"];
self.shotsOnGoal = [aDecoder decodeIntegerForKey:#"ShotsOnGoal"];
Etc.
I have a class named IGMapViewController
In that I have
static IGMapViewController *instance =nil;
+(IGMapViewController *)getInstance {
#synchronized(self) {
if (instance==nil) {
instance= [IGMapViewController new];
}
}
return instance;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// more code
instance = self;
}
return self;
}
If use the object in more then 1 class but only use initWithNibName in one class.
In a class named IGRouteController in the init method i use _mapViewController = [IGMapViewController getInstance]; this happens before the initWithNibName gets executed in another class.
In IGRouteController I have a method updateRouteList in that method I use:
[_mapViewController drawSuggestedRoute:suggestedRoute];
It all does run but I can't see the result.
If i use:
IGMapViewController *wtf = [IGMapViewController getInstance];
[wtf drawSuggestedRoute:suggestedRoute];
Then it does work great.
So is it possible to get a instance and init later with a nib?
I believe I see what you are trying to accomplish. You want to initialize a singleton instance of your class from a nib. Correct?
When you initialize your instance, you are using [IGMapViewController new] which is presumably not the intended behavior. How about this (untested...)?
+ (id)sharedController
{
static dispatch_once_t pred;
static IGMapViewController *cSharedInstance = nil;
dispatch_once(&pred, ^{
cSharedInstance = [[self alloc] initWithNibName:#"YourNibName" bundle:nil];
});
return cSharedInstance;
}
clankill3r,
You should avoid creating singleton UIViewControllers (see comments in this discussion UIViewController as a singleton). This has been also highlighted by #CarlVeazey.
IMHO, you should create a UIViewController each time you need it. In this case your view controller would be a reusable component. When you create a new instance of your controller, just inject (though a property or in the initializer the data you are interested in, suggestedRoute in this case).
A simple example could be the following:
// YourViewController.h
- (id)initWithSuggestedRoute:(id)theSuggestedRoute;
// YourViewController.m
- (id)initWithSuggestedRoute:(id)theSuggestedRoute
{
self = [super initWithNibName:#"YourViewController" bundle:nil];
if (self) {
// set the internal suggested route, e.g.
_suggestedRoute = theSuggestedRoute; // without ARC enabled _suggestedRoute = [theSuggestedRoute retain];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self drawSuggestedRoute:[self suggestedRoute]];
}
For further info about UIViewControllers, I really advice to read two interesting post by #Ole Begemann.
Passing Data Between View Controllers
initWithNibName:bundle: Breaks Encapsulation
Hope that helps.
I've been trying to save strings in Xcode such as email and password so i can open them in different view, So far every attempt for the past 2 weeks have failed.
does anyone have a working way, and if so can you post the code.
Thanks
*edit***
Almost done just having an error here
(MemberPage *)initWithString: (NSString) S {
self = [super init];
if ( self ) {
//DO STUFF;
UserNAME.text = S.text;
}
return self;
}
error is on the first line:
use of undeclared identifier with initWithString
Also get should be a ; before :
(MemberPage *)initWithString: (NSString *) s {
self = [super init];
if ( self ) {
//DO STUFF;
UserNAME = s;
}
return self;
}
Forgot the '*'?
One of the easiest way's I've done this in the past is just to pass them in when I create my View:
RecieverClass.m:
(RecieverClass*) initWithString: (NString) S {
self = [super init];
if ( self ) {
//DO STUFF;
myLocalString = S;
}
return self;
}
SenderClass (where you create your view)
RecieverClass *recieverClass= [[RecieverClass alloc] initWithString:sendString];
[[self navigationController] pushViewController:recieverClass animated:YES];
[recieverClass release];
You could pass them in as pointers or w/e really. Just depends what you're trying to do really.
I use a singleton class to share data between different views and for me it works. In the source viewController, I assign the value as a parameter to the "shared" class, and in the destination viewController I retrieve it.
Dunno if it's the "legal" way to do it, but it's simple and it works.
Check out this tutorial: http://www.bit-101.com/blog/?p=1969
At a certain point you arrive at this piece of code:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
Model *model = [Model sharedModel];
[model addObserver:self forKeyPath:#"text" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
Adding the observer didn't immediately work for me, dunno why but I didn't look into this issue deeper.
I changed it into:
Model *model = [Model sharedModel];
model.parameter = #"btnMainToTarget";
Follow the instruction from beginning till end - it will work.
To any other people; don't hesitate to react if you didn't think my reply was accurate.
Greetings