New to ios programming so for practice I am trying to make a simple app with a playing card with it's back on the view which you can tap to reveal the front of the card. The front of the card is different every time you tap it.
What I am trying to achieve is that when i tap the card:
it flips and displays a random card,
have a label that increments by 1 every time the card is flipped
I am done with the second requirement but I can't seem to get the first one right. I have 3 models(Card, Deck, PlayingCard)
My Deck model is responsible for randomising the cards and here are the codes.
Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
#interface Deck : NSObject
- (void)addCard:(Card *)card atTop:(BOOL)atTop;
- (Card *)drawRandomCard;
#end
Deck.m
#import "Deck.h"
#interface Deck()
#property (strong, nonatomic) NSMutableArray *cards;
#end
#implementation Deck
-(NSMutableArray *)cards
{
if(!_cards) _cards =[[NSMutableArray alloc] init];
return _cards;
}
- (void)addCard:(Card *)card atTop:(BOOL)atTop
{
if(card){
if(atTop){
[self.cards insertObject:card atIndex:0];
}
else
{
[self.cards addObject:card];
}
}
}
- (Card *)drawRandomCard
{
Card *randomCard = nil;
if(self.cards.count){
unsigned index = arc4random() % self.cards.count;
randomCard = self.cards[index];
[self.cards removeObjectAtIndex:index];
}
return randomCard;
}
#end
My PlayingCard basically describes the contents of a Card. for eg. Jack of Hearts, 2 of Diamonds where Jack would be the "rank" of the card and "hearts" would be the suit of the card which together form the contents of the card.
PlayingCard.h
#import <Foundation/Foundation.h>
#import "Card.h"
#interface PlayingCard : NSObject
#property (strong, nonatomic) NSString *suit;
#property (nonatomic) NSUInteger rank;
+ (NSArray *) validSuits;
+ (NSUInteger) maxRank;
- (NSString *)contents;
#end
PlayingCard.m
#import "PlayingCard.h"
#implementation PlayingCard
- (NSString *)contents
{
NSArray *rankStrings = [PlayingCard rankStrings];
return [rankStrings[self.rank] stringByAppendingString:self.suit];
}
#synthesize suit = _suit;
+(NSArray *)validSuits{
return #[#"♥", #"♦", #"♠", #"♣"] ;
}
+(NSArray *)rankStrings{
return #[# "?", #"A", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9",#"10", #"J", #"Q", #"K"];
}
-(void)setSuit:(NSString *)suit
{
if([[PlayingCard validSuits] containsObject:suit]){
_suit = suit;
}
}
-(NSString *)suit{
return _suit ? _suit: #"?";
}
+ (NSUInteger)maxRank {
return [self rankStrings].count-1;
}
-(void)setRank:(NSUInteger)rank{
if(rank <= [PlayingCard maxRank]) {
_rank = rank;
}
}
#end
Finally my ViewController.m
#import "CardGameViewController.h"
#import "Card.h"
#import "Deck.h"
#import "PlayingCard.h"
#interface CardGameViewController ()
#property (weak, nonatomic) IBOutlet UILabel *flipsLabel;
#property (nonatomic) int flipCount;
#property (nonatomic) NSString *title;
#property (weak, nonatomic) IBOutlet UIButton *cardRandom;
#end
#implementation CardGameViewController
- (void)setFlipCount:(int)flipCount
{
_flipCount = flipCount;
self.flipsLabel.text = [NSString stringWithFormat:#" Flips:%d", self.flipCount ];
}
- (IBAction)flipCard:(UIButton *)sender
{
if(sender.isSelected) {
sender.selected = NO;
self.flipCount ++;
}
else{
sender.selected = YES;
self.flipCount ++;
}
}
#end
I researched about this and i found that I should probably use
- (void)setTitle:(NSString *)title forState:(UIControlState)state
where the state will be UIControlStateSelected because the front of the card containing the contents is the selected content of the button but I dont know how to set the title according to the random card content developed by my model.
Lets assume you have a property of the class Deck (called cardDeck) in your CardGameViewController, and you have already populated that property with cards using addCard method.
Now when the user taps flip button, you should do something like this ::
- (IBAction)flipCard:(UIButton *)sender
{
//draw a random card
Card *randomCard = [self.cardDeck drawRandomCard];
//set the button title
[sender setTitle:[randomCard contents] forState:UIControlStateSelected];
if(sender.isSelected) {
sender.selected = NO;
self.flipCount ++;
}
else{
sender.selected = YES;
self.flipCount ++;
}
}
Hope this helps
PS :: drawRandomCard method has a return type "Card", it seems it should be "PlayingCard", or the PlayingCard class should be named Card.
You aren't initializing your playing card deck. Your ViewController.m file should look like this (I also reset the deck once all cards have been shown in the flipCard method):
#import "CardViewViewController.h"
#import "PlayingCardDeck.h"
#interface CardViewViewController ()
#property (weak, nonatomic) IBOutlet UILabel *flipsLabel;
#property (nonatomic) int flipCount;
#property (nonatomic) PlayingCardDeck *myDeck;
#end
#implementation CardViewViewController
-(PlayingCardDeck *)myDeck
{
if(!_myDeck) _myDeck=[[PlayingCardDeck alloc] init];
return _myDeck;
}
-(void)setFlipCount:(int)flipCount
{
_flipCount= (int) flipCount;
self.flipsLabel.text=[NSString stringWithFormat:#"Flips: %d",self.flipCount];
}
- (IBAction)flipCard:(UIButton *)sender
{
if (!sender.isSelected)
{
if (self.flipCount >= 52)
{
self.flipCount = 0;
self.myDeck = nil;
}
Card *cCard = [self.myDeck drawRandomCard];
[sender setTitle: [cCard contents] forState:UIControlStateSelected];
self.flipCount++;
}
sender.selected = !sender.isSelected;
}
#end
Related
In my card matching game:
-I have a method that checking cards that were flipped in certain index. It's basically the whole logic in the app.
-I have another method that checks the matching.
Now, I created a switch button in my view controller that will tell the controller that the user changed the mode for "3" cards instead of the basic mode (2 cards).
My issue is, how do i tell the controller to check in the matching method if there is more than 2 matches..it's driving me crazy please try to help me figure this out.
I also have in the controller an updateUI method that making cards that are matched fade away so I need to make sure it behaves the same.
The following code show's the flipCardAtIndex method, matching method & view controller in the same order:
CardMatchingGame.m (the last method is flipCardAtIndex):
#import "CardMatchingGame.h"
#import "PlayingCardsDeck.h"
#interface CardMatchingGame()
#property (readwrite, nonatomic) int score;
#property (strong, nonatomic) NSMutableArray *cards;
#property (strong, nonatomic) NSString *notification;
#end
#implementation CardMatchingGame
-(NSMutableArray *) cards {
if (!_cards) _cards = [[NSMutableArray alloc] init];
return _cards;
}
-(id)initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck {
self = [super init];
if (self) {
for (int i = 0; i < count; i++) {
Card *card = [deck drawRandonCard];
if (!card) {
self = nil;
} else {
self.cards[i] = card;
}
}
}
return self;
}
-(Card *) cardAtIndex:(NSUInteger)index {
return (index < self.cards.count) ? self.cards[index] : nil;
}
#define FLIP_COST 1
#define MISMATCH_PENALTY 2
#define BONUS 4
-(void) flipCardAtIndex:(NSUInteger)index {
Card *card = [self cardAtIndex:index];
if (!card.isUnplayable) {
if (!card.isFaceUp) {
for (Card *otherCard in self.cards) {
if (otherCard.isFaceUp && !otherCard.isUnplayable) {
NSMutableArray *myCards = [[NSMutableArray alloc] init];
[myCards addObject:otherCard];
int matchScore = [card match:myCards];
if (matchScore) {
otherCard.unplayble = YES;
card.unplayble = YES;
self.notification = [NSString stringWithFormat:#"%# & %# match!", card.contents, otherCard.contents];
self.score += matchScore * BONUS;
} else {
otherCard.faceUp = NO;
self.score -= MISMATCH_PENALTY;
self.notification = [NSString stringWithFormat:#"%# did not matched to %#", card.contents, otherCard.contents];
}
break;
}
}
self.score -= FLIP_COST;
}
card.faceUp = !card.isFaceUp;
}
}
#end
PlayingCards.m (Only the first method, matching method) :
#import "PlayingCards.h"
#implementation PlayingCards
#synthesize suit = _suit;
//overriding the :match method of cards to give different acore if its only a suit match or a number match
-(int)match:(NSArray *)cardToMatch {
int score = 0;
for (int i = 0; i < cardToMatch.count; i++) {
PlayingCards *nextCard = cardToMatch[i];
if ([nextCard.suit isEqualToString:self.suit]) {
score += 1;
} else if (nextCard.rank == self.rank) {
score += 4;
}
}
return score;
}
My view controller (the last method is the one for the switch button) :
#import "CardGameViewController.h"
#import "PlayingCardsDeck.h"
#import "CardMatchingGame.h"
#interface CardGameViewController ()
#property (weak, nonatomic) IBOutlet UILabel *flipsLabel;
#property (weak, nonatomic) IBOutlet UILabel *notificationLabel;
#property (weak, nonatomic) IBOutlet UILabel *scoreCounter;
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
#property (strong, nonatomic) CardMatchingGame *game;
#property (nonatomic) int flipsCount;
#property (nonatomic) NSNumber *mode;
//#property (weak, nonatomic) IBOutlet UISwitch *mySwitch;
#property (weak, nonatomic) IBOutlet UISwitch *mySwitch;
#end
#implementation CardGameViewController
#synthesize mode = _mode;
//creating the getter method that creates a new card game.
-(CardMatchingGame *) game {
if (!_game) _game = [[CardMatchingGame alloc] initWithCardCount:self.cardButtons.count usingDeck:[[PlayingCardsDeck alloc] init]];
return _game;
}
//creating a setter for the IBOutletCollection cardButtons
-(void) setCardButtons:(NSArray *)cardButtons {
_cardButtons = cardButtons;
[self updateUI];
}
//creating the setter for the flipCount property. Whick is setting the flipsLabel to the right text and adding the number of counts.
-(void) setFlipsCount:(int)flipsCount {
_flipsCount = flipsCount;
self.flipsLabel.text = [NSString stringWithFormat:#"Flips: %d", self.flipsCount];
}
-(void) updateUI {
for (UIButton *cardButton in self.cardButtons) {
Card *card = [self.game cardAtIndex:[self.cardButtons indexOfObject:cardButton]];
[cardButton setTitle:card.contents forState:UIControlStateSelected];
[cardButton setTitle:card.contents forState:UIControlStateSelected|UIControlStateDisabled];
cardButton.selected = card.isFaceUp;
cardButton.enabled = !card.unplayble;
if (card.unplayble) {
cardButton.alpha = 0.1;
}
//updating the score
self.scoreCounter.text = [NSString stringWithFormat:#"Score: %d", self.game.score];
//if notification in CardMatchingGame.m is no nil, it will be presented
if (self.game.notification) {
self.notificationLabel.text = self.game.notification;
}
}
}
//Here I created a method to flipCards when the card is selected, and give the user a random card from the deck each time he flips the card. After each flip i'm incrementing the flipCount setter by one.
- (IBAction)flipCard:(UIButton *)sender {
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender] forMode:self.mode];
self.flipsCount++;
[self updateUI];
}
//sending an alert if the user clicked on new game button
- (IBAction)newGame:(UIButton *)sender {
UIAlertView* mes=[[UIAlertView alloc] initWithTitle:#"Are you sure..?" message:#"This will start a new game" delegate:self cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil];
[mes show];
}
//preforming an action according to the user choice for the alert yes/no to start a new game
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [alertView cancelButtonIndex]) {
self.flipsCount = 0;
self.game = nil;
for (UIButton *button in self.cardButtons) {
Card *card = [self.game cardAtIndex:[self.cardButtons indexOfObject:button]];
card.unplayble = NO;
card.faceUp = NO;
button.alpha = 1;
}
self.notificationLabel.text = nil;
[self updateUI];
}
}
-(void) setMode:(NSNumber *)mode {
mode = _mode;
}
-(void) switchValueChange:(id)sender {
UISwitch *Switch = (UISwitch *) sender;
NSNumber *twoCards = [NSNumber numberWithInt:2];
NSNumber *threeCards = [NSNumber numberWithInt:3];
if (Switch.on) {
self.mode = twoCards;
}
else
{
self.mode = threeCards;
}
}
- (void)viewDidLoad
{
UISwitch *mySwitch;
[super viewDidLoad];
[mySwitch addTarget:self action:#selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];
[self updateUI];
}
#end
Actually, getting the switch value is the easy part. You can get set a referencing outlet from your switch to your viewController (CardGameViewController), and in your view controller's viewDidLoad method, add the method to listen for changes to switch's value:
[mySwitch addTarget:self action:#selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];
Add a new property called NSNumber *mode in your CardGameViewController and synthesize it.
Now you can update the "mode" (which I believe could be an instance variable) in switchValueChanged method:
- (void)switchValueChange:(id)sender
{
UISwitch *switch = (UISwitch *) sender;
if (sender.on)
self.mode = 2;
else
self.mode = 3;
}
If my assumption is right, then by "mode" you mean how many cards to match, is that correct? 2 means, that atleat 2 cards should be same when faced up, and 3 means, that 3 cards should match in suit or number.
Start by changing the match method in your PlayingCards to something like this (accepting another parameter named mode) (You might have to update the same method in its parent class too):
//overriding the :match method of cards to give different acore if its only a suit match or a number match
-(int)match:(NSArray *)cardToMatch forMode:(NSNumber *) mode{
int score = 0;
int cardsMatched = 0;
for (int i = 0; i < cardToMatch.count; i++) {
PlayingCards *nextCard = cardToMatch[i];
if ([nextCard.suit isEqualToString:self.suit]) {
score += 1;
cardsMatched++;
} else if (nextCard.rank == self.rank) {
score += 4;
cardsMatched++;
}
if (cardsMatched >= [mode intValue])
break;
}
return score;
}
Now, in your CardMatchingGame.m method, change the flipCardAtIndex method to this (accepting another parameter named mode):
-(void) flipCardAtIndex:(NSUInteger)index forMode (NSNumber *mode) {
Card *card = [self cardAtIndex:index];
if (!card.isUnplayable) {
if (!card.isFaceUp) {
NSMutableArray *myFaceUpCards = [[NSMutableArray alloc] init];
// UPDATED: Loop through all the cards that are faced up and add them to an array first
for (Card *otherCard in self.cards) {
if (otherCard.isFaceUp && !otherCard.isUnplayable && orderCard != card) {
[myCards addObject:otherCard];
}
// UPDATED: Now call the method to do the match. The match method now takes an extra parameter
int matchScore = [card match:myCards forMode:mode];
if (matchScore) {
otherCard.unplayble = YES;
card.unplayble = YES;
self.notification = [NSString stringWithFormat:#"%# & %# match!", card.contents, otherCard.contents];
self.score += matchScore * BONUS;
} else {
otherCard.faceUp = NO;
self.score -= MISMATCH_PENALTY;
self.notification = [NSString stringWithFormat:#"%# did not matched to %#", card.contents, otherCard.contents];
}
}
self.score -= FLIP_COST;
}
card.faceUp = !card.isFaceUp;
}
}
Finally, change call to
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender]];
in
- (IBAction)flipCard:(UIButton *)sender
method of CardGameViewController to
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender] forMode:self.mode];
Take a look and see if that makes sense.
Remove #property (weak, nonatomic) IBOutlet UISwitch *mySwitch; from your .m class.
Instead, go to your storyboard, click on the controller that has the switch, then click on the Assistant Editor button on top right (looks like a face). It will open up CardGameViewController.h. Now right click on the switch view on the storyoard, and click and drag from New Referencing Outlet to CardViewController.h. This is how you reference your switch into your controller.
Now that you have the switch variable in the interface file, go to your implementation file (CardGameViewController.m) and synthesize the variable:
#synthesize mySwitch = _mySwitch;
Now, change your viewDidLoad method to this:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.mySwitch addTarget:self action:#selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];
[self updateUI];
}
Also remove setMode method. If you are synthesizing mode variable, then you don't need it.
Give it a try now. You know how to debug in xcode using breakpoints?
I built a table view, with Custom Table View Cells, where each cell has several UITextFields that allow the user to enter numbers that provide a calculation. The cell hast a subtotal UITextField that provides the result for that cell.
My problem is that I want to take the subtotal of that cell, and of each additional cell that the User uses, and provide a Total on a different UIView.
I do not know how to take the value of each subtotal for each cell, and furthermore, how can I know how many cells the user used, and all of this in order to provide the user with a TOTAL in a different view.
What would be the best way to achieve this?
Here is the Code.
I want to add new cells and be able to add them to the calculation. But each cell will hold different values that the user will provide.
Best!
#import <UIKit/UIKit.h>
#interface ProCalculator : UIViewController <UITableViewDelegate, UITableViewDataSource> {
UITextField *totalField;
UITextField *differenceField;
UITextField *productField;
UITextField *price1Field;
UITextField *discount1P1Field;
UITextField *discount2P1Field;
UITextField *subtotal1Field;
UITextField *product2Field;
UITextField *price2Field;
UITextField *discount1P2Field;
UITextField *discount2P2Field;
UITextField *subtotal2Field;
}
#property (nonatomic, retain) IBOutlet UITextField *totalField;
#property (nonatomic, retain) IBOutlet UITextField *differenceField;
#property (nonatomic, retain) IBOutlet UITextField *productField;
#property (nonatomic, retain) IBOutlet UITextField *price1Field;
#property (nonatomic, retain) IBOutlet UITextField *discount1P1Field;
#property (nonatomic, retain) IBOutlet UITextField *discount2P1Field;
#property (nonatomic, retain) IBOutlet UITextField *subtotal1Field;
#property (nonatomic, retain) IBOutlet UITextField *product2Field;
#property (nonatomic, retain) IBOutlet UITextField *price2Field;
#property (nonatomic, retain) IBOutlet UITextField *discount1P2Field;
#property (nonatomic, retain) IBOutlet UITextField *discount2P2Field;
#property (nonatomic, retain) IBOutlet UITextField *subtotal2Field;
-(IBAction)calculateDiscounts:(id)sender;
-(IBAction)removeKeyboard;
#end
#import "ProCalculator.h"
#import "ProductCell.h"
#implementation ProCalculator
#synthesize totalField, differenceField, productField, price1Field, discount1P1Field, discount2P1Field, subtotal1Field;
#synthesize product2Field, price2Field, discount1P2Field, discount2P2Field, subtotal2Field;
-(IBAction)calculateDiscounts:(id)sender
{
//Calculate product 1 within first Cell
NSString *firstPrice = self.price1Field.text;
NSString *discount1P1 = self.discount1P1Field.text;
NSString *discount2P1 = self.discount2P1Field.text;
double subtotal1WithDiscount1;
double subtotal1WithDiscount2;
double firstPriceDouble = [firstPrice doubleValue];
double discount1P1Double = [discount1P1 doubleValue];
double discount2P1Double = [discount2P1 doubleValue];
double percentageDiscount1P1;
double percentageDiscount2P1;
double savings1P1;
double savings2P1;
percentageDiscount1P1 = discount1P1Double / 100;
percentageDiscount2P1 = discount2P1Double / 100;
savings1P1 = firstPriceDouble * percentageDiscount1P1;
subtotal1WithDiscount1 = firstPriceDouble - savings1P1;
savings2P1 = subtotal1WithDiscount1 * percentageDiscount2P1;
subtotal1WithDiscount2 = subtotal1WithDiscount1 - savings2P1;
NSString *finalSubtotal1 = [[NSString alloc] initWithFormat:#"$ %.02f", subtotal1WithDiscount2];
self.subtotal1Field.text = finalSubtotal1;
//Calculate product 2 within second Cell (Instead of Building more cells, I need to get information from User Input to create Cells and store information from them and add them to the calculation.
NSString *secondPrice = self.price1Field.text;
NSString *discount1P2 = self.discount1P2Field.text;
NSString *discount2P2 = self.discount2P2Field.text;
double subtotal2WithDiscount1;
double subtotal2WithDiscount2;
double secondPriceDouble = [secondPrice doubleValue];
double discount1P2Double = [discount1P2 doubleValue];
double discount2P2Double = [discount2P2 doubleValue];
double percentageDiscount1P2;
double percentageDiscount2P2;
double savings1P2;
double savings2P2;
percentageDiscount1P2 = discount1P2Double / 100;
percentageDiscount2P2 = discount2P2Double / 100;
savings1P2 = secondPriceDouble * percentageDiscount1P2;
subtotal2WithDiscount1 = secondPriceDouble - savings1P2;
savings2P2 = subtotal2WithDiscount1 * percentageDiscount2P2;
subtotal2WithDiscount2 = subtotal2WithDiscount1 - savings2P2;
NSString *finalSubtotal2 = [[NSString alloc] initWithFormat:#"$ %.02f", subtotal2WithDiscount2];
self.subtotal1Field.text = finalSubtotal2;
//Calculate Total
double totalAmount;
totalAmount = subtotal1WithDiscount2 + subtotal2WithDiscount2;
NSString *theTotal = [[NSString alloc] initWithFormat:#"$ %.02f", totalAmount];
self.totalField.text = theTotal;
//Calculate Money Saved
double moneySaved;
moneySaved = savings1P1 + savings2P1 + savings1P2 + savings2P2;
NSString *theMoneySaved = [[NSString alloc] initWithFormat:#"$ %.02f", moneySaved];
self.differenceField.text = theMoneySaved;
}
-(IBAction)removeKeyboard
{
[self.productField resignFirstResponder];
[self.price1Field resignFirstResponder];
[self.discount1P1Field resignFirstResponder];
[self.discount2P1Field resignFirstResponder];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ProductTableViewCell";
ProductCell *cell = (ProductCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLabel = [[NSBundle mainBundle] loadNibNamed:#"ProductTableViewCell" owner:self options:nil];
for (id currentObject in topLabel) {
if ([currentObject isKindOfClass:[ProductCell class]]) {
cell = (ProductCell *) currentObject;
break;
}
}
}
return cell;
}
#end
First of all, I assume you are using a custom UITableViewCell, which has a textfield as one of it's parameters.
I would suggest making an associated model with these cells, which has a parameter which stores the value of the textfield every time the user enters in new information. Something like:
#interface TableViewCellModel : NSObject {
NSString * _valueOfTextfield;
}
#property (nonatomic, retain) NSString * valueOfTextfield;
#end
#implementation TableViewCellModel
#synthesize valueOfTextfield = _valueOfTextfield;
#end
Then, when the textfield is pressed, and the user enters in the information and resigns the textfield, update the associated model of the cell to store the value of the textfield.
When you want to show the TOTAL of the textfields in all the cells, just look at each of the cells corresponding models, and check if the valueOfTextfield property is nil or not.
One warning though, if each of the custom UITableViewCells has their own textfield, you could have some issues with performance, as well as your touch events in the cells. You may want to look into having a singleton class which handles the textfields separately, and each one of the custom cells having a reference to this singleton class.
EDIT: I have played with some code, and I think I have what you want now. You'll need a few files, and this gets a bit long. They will be in .h, then .m pairs.
#interface TableViewModel : NSObject {
int _index;
NSString * _textFieldValue;
}
#property (nonatomic, assign) int index;
#property (nonatomic, retain) NSString * textFieldValue;
#end
#import "TableViewModel.h"
#implementation TableViewModel
#synthesize index = _index;
#synthesize textFieldValue = _textFieldValue;
#end
Then,
#import <UIKit/UIKit.h>
#class TableViewModel;
#interface TableViewCell : UITableViewCell {
IBOutlet UILabel * _label;
}
#property (nonatomic, retain) TableViewModel * tableViewModel;
- (IBAction)useTextField;
#end
#import "TableViewCell.h"
#import "TableViewModel.h"
#import "AppDelegate.h"
#implementation TableViewCell
- (void)dealloc; {
[super dealloc];
}
- (void)setTableViewModel:(TableViewModel *)tableViewModel; {
_label.text = [tableViewModel textFieldValue];
}
- (TableViewModel *)tableViewModel; {
return nil;
}
- (IBAction)useTextField; {
[[AppDelegate appDelegate] useTextFieldForIndex:[self.tableViewModel index]];
}
#end
and finally,
#import <UIKit/UIKit.h>
#class TableViewCell;
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate> {
int _index;
NSMutableArray * _items;
IBOutlet TableViewCell * _cell;
IBOutlet UITableView * _tableView;
IBOutlet UITextField * _textField;
}
- (void)useTextFieldForIndex:(int)aIndex;
#end
#import "ViewController.h"
#import "TableViewModel.h"
#import "TableViewCell.h"
#implementation ViewController
- (void)dealloc; {
[_items release];
[super dealloc];
}
#pragma mark - View lifecycle
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad; {
[super viewDidLoad];
// This is an example of how to fill in the _items array with Models
_items = [[NSMutableArray alloc] init];
TableViewModel * model = [[TableViewModel alloc] init];
model.textFieldValue = #"1";
model.index = 0;
[_items addObject:model];
[model release];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField; {
TableViewModel * model = [_items objectAtIndex:_index];
model.textFieldValue = [_textField.text retain];
[_items replaceObjectAtIndex:_index withObject:model];
[_tableView reloadData];
[_textField resignFirstResponder];
return YES;
}
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; {
return 100;//This should be whatever the height of the cell is!
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; {
return [_items count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; {
NSString * cellIdentifier = #"TableViewCell";
TableViewCell * cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:cellIdentifier owner:self options:nil]; // hooks up _cell for us
cell = _cell;
}
cell.tableViewModel = ((TableViewModel *) [_items objectAtIndex:indexPath.row]);
return cell;
}
- (void)useTextFieldForIndex:(int)aIndex; {
_index = aIndex;
[_textField becomeFirstResponder];
}
#end
I assume that you know how to hook up the nibs, but if you need any more help, just ask. Also, you'll need to add these 2 methods to the AppDelegate.
+ (AppDelegate *)appDelegate; {
return (AppDelegate *)[[UIApplication sharedApplication] delegate];
}
- (void)useTextFieldForIndex:(int)aIndex; {
[self.viewController useTextFieldForIndex:aIndex];
}
This will allow the AppDelegate to pass the actions to have the textfield actions in only one view controller. It also should help with performance. To give a bit of explanation, _items holds all of the models which are used in the tableview. (Side note: when you want the user to add a new cell, all you need to do is add a new model to this array.)
The action: - (IBAction)useTextField; is attached to a clear button, and when it is pushed, it tells the AppDelegate to tell the ViewController to bring up the keyboard, and once you are done with it, you update the model as the index of the cell that was pressed, and reload the tableview.
You can then customize the cells the way you want, and make sure that you match the models as well.
To add up the values in the Models, you need only use:
double sum = 0;
for(TableViewCellModel * model in _items){
sum += [model.valueOfTextfield doubleValue];
}
Hope that helps!
I'm trying to experiment with cocos2d by building a simple card game. I'm not new to iOS development but I am very new to the cocos2d engine. I have 2 custom classes so far:
A card class:
.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Card : NSObject {
CCSprite *faceSprite;
NSString *suit;
NSUInteger value;
float priority;
}
#property (nonatomic, retain) CCSprite *faceSprite;
#property (nonatomic, readonly) NSString *suit;
#property (nonatomic, readonly) NSUInteger value;
#property (nonatomic, assign) float priority;
- (id)initWithSprite:(CCSprite *)paramSprite suit:(NSString *)paramSuit andValue:(NSUInteger)paramValue;
#end
.m
#import "Card.h"
#implementation Card
#synthesize faceSprite, suit, value, priority;
- (id)init
{
self = [self initWithSprite:nil suit:nil andValue:nil];
if (self) {
// Initialization code here.
}
return self;
}
-(id)initWithSprite:(CCSprite *)paramSprite suit:(NSString *)paramSuit andValue:(NSUInteger)paramValue {
if ((self = [super init])) {
faceSprite = paramSprite;
suit = paramSuit;
value = paramValue;
}
return self;
}
#end
And a Deck class:
.h
#import <Foundation/Foundation.h>
#import "Card.h"
#interface Deck : NSObject {
NSMutableArray *cardsArray;
}
#property (nonatomic, retain) NSMutableArray *cardsArray;
-(void)shuffle;
-(Card *)drawTopCard;
#end
.m
- (id)init
{
if ((self = [super init])) {
cardsArray = [[NSMutableArray alloc] init];
for (NSInteger suit = 1; suit < 5; suit++) {
NSString *suitString;
switch (suit) {
case 1:
suitString = #"h";
break;
case 2:
suitString = #"d";
break;
case 3:
suitString = #"s";
break;
case 4:
suitString = #"c";
break;
}
for (NSInteger i = 3; i < 14; i++) {
CCSprite *cardImage = [CCSprite spriteWithFile:[NSString stringWithFormat:#"%d%#.gif",i, suitString]];
Card *card = [[Card alloc] initWithSprite:cardImage suit:suitString andValue:i];
[cardsArray addObject:card];
}
}
}
return self;
}
-(void)shuffle {
int timesToShuffle = 1;
while (++timesToShuffle < 4) {
for (int i = 0; i < [cardsArray count]; i++) {
int cardToSwap = arc4random() % [cardsArray count];
[cardsArray exchangeObjectAtIndex:i withObjectAtIndex:cardToSwap];
}
}
}
-(Card *)drawTopCard {
Card *cardDrawn = [[cardsArray objectAtIndex:0] retain];
//[cardsArray removeObjectAtIndex:0];
return cardDrawn;
}
#end
When I try to do the following in my main scene, I get a EXC_BAD_ACCESS error inside CCScheduler.m in the update:(ccTime) method
-(id) init
{
if( (self=[super init])) {
deck = [[Deck alloc] init];
[deck shuffle];
[self schedule:#selector(drawAndShowCard) interval:3.0];
}
return self;
}
-(void)drawAndShowCard {
// ask director the the window size
CGSize size = [[CCDirector sharedDirector] winSize];
// position the label on the center of the screen
Card *card = [deck drawTopCard];
CCSprite *cardSprite = (CCSprite *)card.faceSprite;
cardSprite.position = ccp( size.width /2 , size.height/2 );
[self addChild:cardSprite];
}
strange thing is if I move the deck alloc init and the shuffle inside the drawCardAndShow method, it no longer crashes, however this is not the correct place for this for OBVIOUS reasons as I don't want a whole new deck every time I draw a card...
any help?
I see one serious problem but you have multiple issues with your code.
When you have strings as properties you should declare them as copy, in your case you have it as assign:
#property (nonatomic, readonly) NSString *suit;
If you want to declare the property readonly, you can once again declare the property inside your .m to make it writeable internally:
#interface Card()
#property (nonatomic, copy)
NSString *suit;
#end
You should use the self. notation for properties to make your intent clear to others when you are accessing the ivar and when accessing the property.
You got a mem leak here:
Card *card = [[Card alloc] initWithSprite:cardImage suit:suitString andValue:i];
[cardsArray addObject:card];
-->missing [card release]
This here is unnecessary
#interface Deck : NSObject {
NSMutableArray *cardsArray;
}
#property (nonatomic, retain) NSMutableArray *cardsArray;
You can omit the ivar, just make sure you do not alloc/init without autorelease.
If you insist on having an ivar rename at least to _cardsArray and then
#synthesize cardsArray=_cardsArray
so that you can distinguish between the two. In one case you need to explicit to retain, in the other you use the setter/getter.
hth
you should set deck as a property
Okay, I'm totally stumped here.
This works in CouponListViewController.m:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.couponList = [CouponDatabase database].couponList;
self.title = #"Coupon List";
}
And this works in CouponDetailViewController.m:
- (void)viewWillAppear:(BOOL)animated {
CouponDetails *details = [[CouponDatabase database] couponDetails:_uniqueId];
if (details != nil) {
[_merchantNameLabel setText:details.merchantName];
[_longDealLine1Label setText:details.longDealLine1];
//....blah...blah//
}
}
But when I change the CouponDatabase.h from this (which works with the above):
#class CouponDetails;
#interface CouponDatabase : NSObject {
sqlite3 *_database;
}
+ (CouponDatabase *)database;
- (NSArray *)couponList;
- (CouponDetails *)couponDetails:(int) uniqueId;
...to this (which works if I manually set the value of 'selectedCategory' inside the method):
#class CouponList;
#class CouponDetails;
#interface CouponDatabase : NSObject {
sqlite3 *_database;
}
+ (CouponDatabase *)database;
- (CouponList *)couponList:(int) selectedCategory;
- (CouponDetails *)couponDetails:(int) uniqueId;
and then change CouponListViewController.m to this:
1 - (void)viewWillAppear:(BOOL)animated {
2 [super viewWillAppear:animated];
3 self.couponList = [[CouponDatabase database] couponList:_selectedCategory];
4 self.title = #"Coupon List";
5 }
I get this error on line 3 above:
warning: incompatible Objective-C types 'struct CouponList *',
expected 'struct NSArray *' when passing argument 1 of 'setCouponList:'
from distinct Objective-C type
Question: What is the proper formatting of the 'self.couponlist' line so that I can pass an integer to the CouponDatabase for use in the couponList method?
EDIT: I'm aware that couponDetails is now a class instead of an array - I just don't know know how to format the line to initialize the table data.
I hope this makes sense - any help on this would be very greatly appreciated.
Thanks in advance!
Adding CouponListViewController.h:
#import <UIKit/UIKit.h>
#class CouponDetailsViewController;
#interface CouponListViewController : UITableViewController {
NSArray *_couponList;
CouponDetailsViewController *_couponDetails;
int _selectedCategory;
}
#property (nonatomic, retain) NSArray *couponList;
#property (nonatomic, retain) CouponDetailsViewController *couponDetails;
#property(nonatomic, assign) int selectedCategory;
#end
Try changing your CouponListViewController.h to this:
#import <UIKit/UIKit.h>
#class CouponDetailsViewController;
#interface CouponListViewController : UITableViewController {
CouponList *_couponList;
CouponDetailsViewController *_couponDetails;
int _selectedCategory;
}
#property (nonatomic, retain) CouponList *couponList;
#property (nonatomic, retain) CouponDetailsViewController *couponDetails;
#property(nonatomic, assign) int selectedCategory;
#end
Oops I put my response in my own original post and should have put it here:
Edit: Okay, I made changes to CouponListViewController.h as recommended by Robert, plus added #class CouponList; as follows:
#import <UIKit/UIKit.h>
#class CouponList;
#class CouponDetailsViewController;
#interface CouponListViewController : UITableViewController {
CouponList *_couponList;
CouponDetailsViewController *_couponDetails;
int _selectedCategory;
}
#property (nonatomic, retain) CouponList *couponList;
#property (nonatomic, retain) CouponDetailsViewController *couponDetails;
#property(nonatomic, assign) int selectedCategory;
#end
I'm still getting errors in CouponListViewController.m:
#import "CouponListViewController.h"
#import "CouponDatabase.h"
#import "CouponList.h"
#import "CouponDetailsViewController.h"
#implementation CouponListViewController
#synthesize couponList = _couponList;
#synthesize couponDetails = _couponDetails;
#synthesize selectedCategory = _selectedCategory;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.couponList = [CouponDatabase database].couponList; // <--- ERROR 1
self.title = #"Coupon List";
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_couponList count]; // <--- WARNINGS 1 AND 2
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.couponDetails == nil) {
self.couponDetails = [[[CouponDetailsViewController alloc] initWithNibName:#"CouponDetailsViewController" bundle:nil] autorelease];
}
CouponList *info = [_couponList objectAtIndex:indexPath.row]; // <--- WARNING 3
NSLog(#"%#", info.uniqueId);
_couponDetails.uniqueId = info.uniqueId;
[self.navigationController pushViewController:_couponDetails animated:YES];
}
ERROR 1: request for member 'couponList' in something not a structure or union
WARNING 1: 'CouponList' may not respond to '-count'
WARNING 2: return makes integer from pointer without a cast
WARNING 3: 'CouponList' may not respond to '-objectAtIndex:'
In you original code for CouponDatabase, you are changing the definition:
- (NSArray *)couponList;
for this one:
- (CouponList *)couponList:(int) selectedCategory;
Nevertheless, you use that return value as datasource for a list view controller, so . Here you have a mismatch you should fix. How to fix it depends on the semantics of your application. What are your trying to do with - (CouponList *)couponList:(int) selectedCategory;? What does really return this selector? What is the interface CouponList? Possibly you should change the line:
self.couponList = [[CouponDatabase database] couponList:_selectedCategory];
so that it returns an NSArray build from a CouponList. But I am not sure of the semantics of your objects, so this might not be the case.
I'd like to subclass UIButton to add some properties that i need (not methods... only properties).
Here the code of my subclass:
//.h-----------------------
#interface MyButton : UIButton{
MyPropertyType *property;
}
#property (nonatomic,retain) MyPropertyType *property;
#end
//.m--------------------------
#implementation MyButton
#synthesize property;
#end
And here how I use the class:
MyButton *btn = ((MytButton *)[MyButton buttonWithType:UIButtonTypeRoundedRect]);
btn.property = SomeDataForTheProperty;
From where i obtain this error :
-[UIRoundedRectButton setProperty:]: unrecognized selector sent to instance 0x593e920
Thus, from ButtonWithType i obtain a UIRoundedRectButton and (Mybutton *) can't cast it...
What i have to do to obtain a MyButton object ? is -init the unique solution ?
Thank you!
Try using a category with Associative References instead. It is much cleaner and will work on all instances of UIButton.
UIButton+Property.h
#import <Foundation/Foundation.h>
#interface UIButton(Property)
#property (nonatomic, retain) NSObject *property;
#end
UIButton+Property.m
#import "UIButton+Property.h"
#import <objc/runtime.h>
#implementation UIButton(Property)
static char UIB_PROPERTY_KEY;
#dynamic property;
-(void)setProperty:(NSObject *)property
{
objc_setAssociatedObject(self, &UIB_PROPERTY_KEY, property, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSObject*)property
{
return (NSObject*)objc_getAssociatedObject(self, &UIB_PROPERTY_KEY);
}
#end
//Example usage
#import "UIButton+Property.h"
...
UIButton *button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button1.property = #"HELLO";
NSLog(#"Property %#", button1.property);
button1.property = nil;
NSLog(#"Property %#", button1.property);
You need to do:
MyButton *btn = [[MyButton alloc] init];
To create your button. The buttonWithType:UIButtonTypeRoundedRect only creates UIButton objects.
=== edit ===
If you wish to use a RoundedRect button; then I would suggest the following. Basically, we just create a UIView with whatever properties we want and add the desired button to that view.
.h
#interface MyButton : UIView
{
int property;
}
#property int property;
#end
.m
#implementation MyButton
#synthesize property;
- (id)initWithFrame:(CGRect)_frame
{
self = [super initWithFrame:_frame];
if (self)
{
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.frame = self.bounds;
[self addSubview:btn];
}
return self;
}
#end
Usage:
MyButton *btn = [[MyButton alloc] initWithFrame:CGRectMake(0, 0, 200, 20)];
btn.property = 42;
[self.view addSubview:btn];
I have a simple scheme that only involves a few library methods, no boilerplate, and just 3 lines of code for each property you want to add. There are two example properties added below: startPoint and tileState. For illustrative purposes here are the lines you'd need to add for a property like tileState:
//#property (assign, nonatomic) SCZTileState tileState; // tileState line 1
//#property (assign, nonatomic) SCZTileState tileState; // tileState line 2
//#dynamic tileState; // tileState line 3
There's more details in my blog post describing how this works
UIButton+SCZButton.h
#import <UIKit/UIKit.h>
#interface UIButton (SCZButton)
#property (readwrite, nonatomic) id assocData;
#end
UIButton+SCZButton.m
// UIButton+SCZButton.m
// Copyright (c) 2013 Ooghamist LLC. All rights reserved.
#import "UIButton+SCZButton.h"
#import <objc/runtime.h>
#implementation UIButton (SCZButton)
- (id)assocData {
id data = objc_getAssociatedObject(self, "SCZButtonData");
return data;
}
- (void)setAssocData:(id)data {
objc_setAssociatedObject(self, "SCZButtonData", data,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#end
OOGTotallyTile.h
// UIButton+OOGTotallyTile.m
// Copyright (c) 2013 Ooghamist LLC. All rights reserved.
#import <UIKit/UIKit.h>
#import "UIButton+SCZButton.h"
#define kPointLabelTag 837459
typedef enum {
SCZTileStatePlaced,
SCZTileStateDropping,
SCZTileStateDropped
} SCZTileState;
#interface SCZButtonData : NSObject
#property (assign, nonatomic) CGPoint startPoint;
#property (assign, nonatomic) SCZTileState tileState; // tileState line 1
#end
#interface UIButton (OOGTotallyTile)
#property (readonly, nonatomic) SCZButtonData *buttonData;
#property (assign, nonatomic) CGPoint startPoint;
#property (assign, nonatomic) SCZTileState tileState; // tileState line 2
#end
OOGTotallyTile.m
// UIButton+OOGTotallyTile.m
// Copyright (c) 2013 Ooghamist LLC. All rights reserved.
#import "OOGTotallyTile.h"
#implementation SCZButtonData
#end
#implementation UIButton (OOGTotallyTile)
#dynamic startPoint;
#dynamic tileState; // tileState line 3
- (SCZButtonData*)buttonData {
if ( ! self.assocData) {
self.assocData = [[SCZButtonData alloc] init];
}
return self.assocData;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
id forwardingTarget = [super forwardingTargetForSelector:aSelector];
if ( ! forwardingTarget) {
return [self buttonData];
}
return forwardingTarget;
}
#end