I have an Array with 10 Objects. I take the first Object and put it into my Label with a String.
Then I want to have a Method that increases the objectAtIndex by 1.
This is my Code :
//.m
#interface GameViewController () {
NSInteger _labelIndex;
}
#property (nonatomic) NSArray *playerArray;
#property (nonatomic) NSString *playerLabel;
- (void)viewDidLoad {
[super viewDidLoad];
self.playerArray = [NSArray arrayWithObjects:#"FIRST", #"SECOND", #"THIRD", #"FOURTH", #"FIFTH", #"SIXT", #"SEVENTH", #"EIGTH", #"NINTH", #"TENTH", nil];
_labelIndex = 0;
[self updateTurnLabel];
self.turnLabel.text = [NSString stringWithFormat:#"YOUR %# DRAW?", self.playerLabel];
}
Here I call the Method i another Method:
-(void) flipDraws {
self.boardView.userInteractionEnabled = NO;
[self updateTurnLabel];
CardView *cv1 = (CardView *) self.turnedDrawViews[0];
CardView *cv2 = (CardView *) self.turnedDrawViews[1];
}
This is my Method:
-(void) updateTurnLabel {
self.playerLabel = [self.playerArray objectAtIndex:_labelIndex % self.playerArray.count]; _labelIndex++;
}
I tried it with a for Loop but nothing happened. I tried it with just set the objectAtIndex:1 but my Method was not called.
What I am doing wrong?
{
int a;
}
- (void)viewDidLoad {
[super viewDidLoad];
a = 0;
self.playerArray = [NSArray arrayWithObjects:#"FIRST", #"SECOND", #"THIRD", #"FOURTH", #"FIFTH", #"SIXT", #"SEVENTH", #"EIGTH", #"NINTH", #"TENTH", nil];
self.playerLabel = [self.playerArray objectAtIndex:a];
self.turnLabel.text = [NSString stringWithFormat:#"YOUR %# DRAW?", self.playerLabel];
}
-(void) updateTurnLabel {
a +=1;
if (!a<[playerArray count])
{
a = 0;
}
self.playerLabel = [self.playerArray objectAtIndex:a];
}
call self.turnLabel.text = [NSString stringWithFormat:#"YOUR %# DRAW?", self.playerLabel]; after [self updateTurnLabel];
What are you adding +1 to in the method objectAtIndex:.
You should be maintaining a variable which tracks the current index being used, then in your method use this :
-(void) updateTurnLabel {
self.playerLabel = [self.playerArray objectAtIndex:currentIndex+1];
}
Related
I have a NSString which holds info like this:
%hook APMIntro
- (bool)isIntroductoryOffer {
return NO;
}
%end
%hook APMIntro
- (void)setIntroductoryOffer:(bool)arg1 {
arg1 = NO;
%orig;
}
%end
%hook ChallengeProgressHolder
- (void)setHasItem:(bool)arg1 {
arg1 = NO;
%orig;
}
%end
%hook ChallengeProgressHolder
- (bool)hasItem {
return NO;
}
%end
The end result I'm trying to achieve is this
%hook APMIntro
- (bool)isIntroductoryOffer {
return NO;
}
- (void)setIntroductoryOffer:(bool)arg1 {
arg1 = NO;
%orig;
}
%end
%hook ChallengeProgressHolder
- (void)setHasItem:(bool)arg1 {
arg1 = NO;
%orig;
}
- (bool)hasItem {
return NO;
}
%end
So far, I've tried using NSMutableArray to separate the different lines to try and organize them - but the result is an infinite loop. I've tried this code so far
-(NSString *)cleanUp:(NSString *)cleanUp{
NSMutableArray *content = [[cleanUp componentsSeparatedByString:#"%hook "] mutableCopy];
// NSMutableArray *content = [[NSMutableArray alloc] init];
NSMutableArray *cleaned = [[NSMutableArray alloc] init];
NSMutableArray *hooks = [[NSMutableArray alloc] init];
// NSArray *messy = [[cleanUp componentsSeparatedByString:#"\n"] mutableCopy];
cleanUp = #"";
NSString *line, *line1;
//NSString *fixMe = #"";
for (unsigned h = 1; h < content.count; h++){
line = [content objectAtIndex:h-1];
if ([line hasPrefix:#"#import"]){
[cleaned addObject:[NSString stringWithFormat:#"%#\n", line]];
continue;
} else {
cleanUp = [NSString stringWithFormat:#"%#%%hook %#", cleanUp, line];
//[content addObject:[NSString stringWithFormat:#"%hook %#", line]];
}
}
//NSLog(#"\n\n\n%#\n\n\n", cleanUp);
BOOL lookingForEnd = NO;
BOOL hookFound = NO;
hooks = [[cleanUp componentsSeparatedByString:#"\n"] mutableCopy];
// NSString *hook;
//NSLog(#"\n\n\n%#\n\n\n", hooks);
for (unsigned i = 1; i < hooks.count; i++){
line = [hooks objectAtIndex:i-1];
//NSLog(#"%i: %#\n\n", i, line);
if (lookingForEnd) {
[cleaned addObject:[NSString stringWithFormat:#"%#\n", line]];
if ([line isEqualToString:#"%end"]){
lookingForEnd = NO;
//i = 1;
continue;
} else if ([line1 isEqualToString:line]){
lookingForEnd = YES;
}
}
if ([line hasPrefix:#"%hook"]){
line1 = line;
for (unsigned j = 1; j < hooks.count; j++){
printf("\n\n\nHOOK\n\n\n");
if ([[hooks objectAtIndex:j-1] isEqual:line1]){
printf("\n\n\nFOUND\n\n\n");
[hooks addObject:line];
[cleaned addObject:[NSString stringWithFormat:#"%#\n", line]];
lookingForEnd = YES;
hookFound = YES;
//break;
}
}
}
}
NSLog(#"%#\n", cleaned);
return cleaned;
}
After setting this up and reading further into how to sort arrays - I found that I'm going about this all wrong - I'm just not sure how to go about doing it right.
Thanks in advance - any help is greatly appreciated
You possibly could do this all with a complex RegEx -- but, here's a bit of a "step by step" approach.
Create a NSObject class with two string properties - blockName and blockBody.
We'll use this regular expression:
(^%hook )(.*?)(?=%end$)
to split the string into "pieces" of text contained between "%hook " and "%end".
Then, we'll loop through the matches creating an array of "block" objects, each with the name and body.
Next, we sort the array by blockName.
Finally, we loop through the sorted array, formatting an output string with the "bodies" grouped into the named blocks.
So, if we have:
MyBlock.h
#interface MyBlock : NSObject
#property (strong, nonatomic) NSString *blockName;
#property (strong, nonatomic) NSString *blockBody;
#end
MyBlock.m
#import "MyBlock.h"
#implementation MyBlock
#end
and we start with your sample NSString:
%hook APMIntro
- (bool)isIntroductoryOffer {
return NO;
}
%end
%hook APMIntro
- (void)setIntroductoryOffer:(bool)arg1 {
arg1 = NO;
%orig;
}
%end
%hook ChallengeProgressHolder
- (void)setHasItem:(bool)arg1 {
arg1 = NO;
%orig;
}
%end
%hook ChallengeProgressHolder
- (bool)hasItem {
return NO;
}
%end
We can do this:
NSString *input = [self sampleString];
NSMutableArray <MyBlock *> *blocks = [NSMutableArray new];
NSString *pattern = #"(^%hook )(.*?)(?=%end$)";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionDotMatchesLineSeparators | NSRegularExpressionAnchorsMatchLines error:NULL];
NSArray *myArray = [regex matchesInString:input options:0 range:NSMakeRange(0, [input length])] ;
for (NSTextCheckingResult *match in myArray) {
// get the range starting after "%hook "
NSRange matchRange = [match rangeAtIndex:2];
// get the "block" string
NSString *s = [input substringWithRange:matchRange];
// split it by newLines
NSArray <NSString *> *a = [s componentsSeparatedByString:#"\n"];
// we want the "body" to skip the first line
NSRange r;
r.location = 1;
r.length = [a count] - 1;
// get the lines excluding the first line
NSArray <NSString *> *a2 = [a subarrayWithRange:r];
// new MyBlock object
MyBlock *b = [MyBlock new];
b.blockName = [a firstObject];
b.blockBody = [a2 componentsJoinedByString:#"\n"];
[blocks addObject:b];
}
// sort the array of blocks by blockName
NSArray <MyBlock *> *sortedArray;
sortedArray = [blocks sortedArrayUsingComparator:^NSComparisonResult(MyBlock *a, MyBlock *b) {
return [a.blockName compare:b.blockName];
}];
NSMutableString *output = [NSMutableString new];
NSString *curName = #"";
// loop through the array of blocks
for (int i = 0; i < [sortedArray count]; i++) {
MyBlock *b = sortedArray[i];
// if we're at a "new" blockName
if (![curName isEqualToString:b.blockName]) {
curName = b.blockName;
// add %end if output is not ""
if ([output length] != 0) {
[output appendString:#"%end\n"];
}
// add a new line of %hook blockName
[output appendFormat:#"\n%%hook %#\n", curName];
}
// add the block body
[output appendFormat:#"%#", b.blockBody];
}
// "close" the last block
[output appendString:#"%end\n"];
// log the resulting string
NSLog(#"%#", output);
and the output from that sample is:
%hook APMIntro
- (bool)isIntroductoryOffer {
return NO;
}
- (void)setIntroductoryOffer:(bool)arg1 {
arg1 = NO;
%orig;
}
%end
%hook ChallengeProgressHolder
- (void)setHasItem:(bool)arg1 {
arg1 = NO;
%orig;
}
- (bool)hasItem {
return NO;
}
%end
Play with the output formatting (line breaks, spacing, etc) as desired :)
Hi everyone iam new in objective c, now i try to create the app like "What's the word". I find this tutorial and lerned it as well as i can. But i have some problems. I want when i click on buttons the currentTitle replace the lable in placesView. Button click method in LettersView.m named as "displayChar". I have success in getting currentTitle but now i don't know how to pass it to GameController and paste text on "places".
I will be grateful for any help!
Here is my code
LettersView.h
#import <UIKit/UIKit.h>
#class LettersView;
#protocol LetterClickDelegateProtocol <NSObject>
-(void)letterView:(LettersView*)letterView addChar:(NSString *)addChar;
#end
#interface LettersView : UIImageView
#property (strong, nonatomic, readonly) NSString* letter;
#property (assign, nonatomic) BOOL isMatched;
#property (strong, nonatomic) NSString *clickLetter;
#property (weak, nonatomic) id<LetterClickDelegateProtocol> clickDelegate;
#property (strong, nonatomic) UIButton *lblChar;
-(instancetype)initWithLetter:(NSString*)letter andSideLength:(float)sideLength;
#end
LettersView.m
#import "LettersView.h"
#import "config.h"
#implementation LettersView{
NSInteger _xOffset, _yOffset;
}
- (id)initWithFrame:(CGRect)frame
{
NSAssert(NO, #"Use initWithLetter:andSideLength instead");
return nil;
}
-(instancetype)initWithLetter:(NSString*)letter andSideLength:(float)sideLength
{
//the letter background
UIImage* img = [UIImage imageNamed:#"btn_letter#2x.png"];
//create a new object
self = [super initWithImage:img];
if (self != nil) {
//resize the letters
float scale = sideLength/img.size.width;
self.frame = CGRectMake(0,0,img.size.width*scale, img.size.height*scale);
UIButton *lblChar = [[UIButton alloc] initWithFrame:self.bounds];
lblChar.tintColor = [UIColor blackColor];
lblChar.backgroundColor = [UIColor clearColor];
[lblChar setTitle:letter forState:UIControlStateNormal];
[lblChar addTarget:self action:#selector(displaychar:)forControlEvents:UIControlEventTouchUpInside];
[self addSubview:lblChar];
self.isMatched = NO;
_letter = letter;
self.userInteractionEnabled = YES;
}
return self;
}
-(void)displayChar:(id)sender {
UIButton *lblChar = (UIButton *)sender;
NSLog(#" The button's title is %#.", lblChar.currentTitle);
_clickLetter = lblChar.currentTitle;
if (self.clickDelegate) {
[self.clickDelegate letterView:self addChar:lblChar.currentTitle];
}
NSLog(#"hu %#", _clickLetter);
}
PlacesView.h
// PlacesView.m
#import "PlacesView.h"
#import "config.h"
#implementation PlacesView
-(id)initWithFrame:(CGRect)frame {
NSAssert(NO, #"Use initwithletter");
return nil;
}
-(instancetype)initWithLetter:(NSString *)letter andSideLength:(float)sideLength
{
UIImage *img = [UIImage imageNamed:#"btn_input#2x.png"];
self = [super initWithImage: img];
if (self != nil) {
self.isMatched = NO;
float scale = sideLength/img.size.width;
self.frame = CGRectMake(0, 0, img.size.width*scale, img.size.height*scale);
//bullshit time
_fieldForLetter = [[UILabel alloc] initWithFrame:self.bounds];
_fieldForLetter.textAlignment = NSTextAlignmentCenter;
_fieldForLetter.textColor = [UIColor blackColor];
_fieldForLetter.backgroundColor = [UIColor clearColor];
_fieldForLetter.text = #"*"; // if button pressed button title placed here.
[self addSubview:_fieldForLetter];
_letter = letter;
}
return self;
}
#end
GameController.m
#import "GameController.h"
#import "config.h"
#import "LettersView.h"
#import "PlacesView.h"
#import "AppDelegate.h"
#implementation GameController {
//tile lists
NSMutableArray* _letters;
NSMutableArray* _places;
}
-(instancetype)init {
self = [super init];
if (self != nil) {
self.points = [[PointsController alloc] init];
self.audioController = [[AudioController alloc] init];
[self.audioController preloadAudioEffects: kAudioEffectFiles];
}
return self;
}
-(void)dealRandomWord {
NSAssert(self.level.words, #"Level not loaded");
// random word from plist
NSInteger randomIndex = arc4random()%[self.level.words count];
NSArray* anaPair = self.level.words[ randomIndex ];
NSString* word1 = anaPair[1]; // answer
NSString* word2 = anaPair[2]; // some letters
_helpstr = anaPair[3]; // helper
NSLog(#"qweqweq %# %#" , word1 , word2);
NSInteger word1len = [word1 length];
NSInteger word2len = [word2 length];
NSLog(#"phrase1[%li]: %#", (long)word1len, word1);
NSLog(#"phrase2[%li]: %#", (long)word2len, word2);
//calculate the letter size
float letterSide = ceilf( kScreenWidth*0.9 / (float)MAX(word1len, word2len) ) - kTileMargin;
//get the left margin for first letter
float xOffset = (kScreenWidth - MAX(word1len, word2len) * (letterSide + kTileMargin))/2;
//adjust for letter center
xOffset += letterSide/2;
float yOffset = 1.5* letterSide;
// init places list
_places = [NSMutableArray arrayWithCapacity: word1len];
// create places
for (NSInteger i = 0; i<word1len; i++){
NSString *letter = [word1 substringWithRange:NSMakeRange(i, 1)];
if (![letter isEqualToString:#" "]) {
PlacesView* place = [[PlacesView alloc] initWithLetter:letter andSideLength:letterSide];
place.center = CGPointMake(xOffset + i*(letterSide + kTileMargin), kScreenHeight/4);
[self.gameView addSubview:place];
[_places addObject: place];
}
}
//init letters list
_letters = [NSMutableArray arrayWithCapacity: word2len];
//create letter
for (NSInteger i=0;i<word2len;i++) {
NSString* letter = [word2 substringWithRange:NSMakeRange(i, 1)];
if (![letter isEqualToString:#" "]) {
LettersView* letv = [[LettersView alloc] initWithLetter:letter andSideLength:letterSide];
letv.center = CGPointMake(xOffset + i * (letterSide + kTileMargin), kScreenHeight); // "/3*4"
if (i > 6) {
letv.center = CGPointMake(-5.15 * xOffset + i * (letterSide + kTileMargin), kScreenHeight + yOffset); // "/3*4"
}
letv.clickDelegate = self;
[self.gameView addSubview:letv];
[_letters addObject: letter];
}
}
}
-(void)letterView:(LettersView *)letterView addChar:(NSString *)addChar
{
PlacesView* placesView = nil;
for (PlacesView* pl in _places) {
//if (CGRectContainsPoint(pl.frame, pt)) {
if () {
//placesView = pl;
placesView.fieldForLetter.text = letterView.lblChar.currentTitle;
break;
}
}
//1 check if target was found
if (placesView!=nil) {
//2 check if letter matches
if ([placesView.letter isEqualToString: letterView.letter]) {
[self placeLetter:letterView atTarget:placesView];
[self.audioController playEffect: kSoundLetterTap];
self.points.points += self.level.coinsPerLvl; //ne nado tak
NSLog(#"Current points %d" , self.points.points);
[self checkForSuccess];
} else {
[self.audioController playEffect:kSoundFail];
[self addAlert:#"ne success" andMessage:#"You lose!" andButton:#"eshe cyka"];
}
}
}
-(void)placeLetter:(LettersView*)letterView atTarget:(PlacesView*)placeView {
placeView.isMatched = YES;
letterView.isMatched = YES;
letterView.userInteractionEnabled = NO;
}
-(void)checkForSuccess {
for (PlacesView* p in _places) {
//no success, bail out
if (p.isMatched==NO) return;
}
NSLog(#"ya!");
[self addAlert:#"Success" andMessage:#"You win!" andButton:#"eshe cyka"];
[self.audioController playEffect:kSoundSuccess];
}
-(void)addAlert: (NSString *)addTitle andMessage: (NSString *)alertMessage andButton: (NSString *)alertButton {
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow* window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = [UIViewController new];
window.windowLevel = UIWindowLevelAlert + 1;
UIAlertController *alert = [UIAlertController alertControllerWithTitle: addTitle message:alertMessage preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *defaultAction= [UIAlertAction actionWithTitle:alertButton style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
window.hidden = YES;
}];
[alert addAction:defaultAction];
[window makeKeyAndVisible];
[window.rootViewController presentViewController:alert animated:YES completion:nil];
});
}
#end
Your GameController needs to keep a reference to PlacesView. It can also assign the action into LettersView so when the button in LettersView is pressed, the GameController will fetch it and perform an action in PlacesView. GameController is what both the other classes have in common, so it can handle any actions between them.
Another option is to use NSNotificationCenter and post a message in LettersView when the button is pressed, and listen for it in PlacesView.
Yet anther way is using a delegates where GameController makes sure that PlacesView is set as the delegate. When LettersView's button is pressed, it will call the delegate method which PlacesView listens to.
I'd go with first option.
Have a question about blocks in objective-c.
For example I have a list of actions.
I'm initializing an array of blocks:
self.actions = #[
^() { [self showObject:self.object_1]; },
^() { [self showObject:self.object_2]; },
^() { [self showObject:self.object_3]; }
];
And calling them when some row is pressed:
- (void)pressedRowAtIndex:(NSInteger)index {
if (index < actions.count) {
void (^action)() = [actions objectAtIndex:index];
if (action != nil) {
action();
}
}
}
And all works fine without problem. But when I init my actions array by using initWithObjects method:
self.actions = [NSArray alloc] initWithObjects:
^() { [self showObject:self.object_1]; },
^() { [self showObject:self.object_2]; },
^() { [self showObject:self.object_3]; },
nil
];
Than I get crash trying to get action by index by using objectAtIndex method of NSArray class.
I understand the difference between this inits. First one don't increase reference count like first do. But can someone explain why it crash?
Edit:
All that I've found. Maybe I'm nub and somewhere else is another useful information.
There is no crash info in terminal:
Code for Onik IV:
Small example:
#interface ViewController () {
NSArray *actions;
}
#property (nonatomic, strong) NSString *object1;
#property (nonatomic, strong) NSString *object2;
#property (nonatomic, strong) NSString *object3;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
actions = [[NSArray alloc] initWithObjects:
^() { [self showObject:self.object1];},
^() { [self showObject:self.object2]; },
^() {[self showObject:self.object3]; },
nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.object1 = #"object 1";
self.object2 = #"object 2";
self.object3 = #"object 3";
void(^firsSimpleBlock)(void) = [actions lastObject];
firsSimpleBlock();
void(^simpleBlock)(void) = [actions firstObject];
simpleBlock();
}
-(void)showObject:(NSString *)object
{
NSLog(#"Show: %#",object);
}
#end
Try something like this.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
(^someBlock)(void) = ^void(void){
self.object1;
};
actions = [[NSArray alloc] initWithObjects:
[someBlock copy],
[someOtherBlock copy],
[anotherBlock copy],
nil];
}
Blocks are allocated on the stack and are therefor removed when the frame is removed from the stack leading to sail pointers for all pointers pointing to that block. When you allocate a object with the literal "#" sign the object is allocated in a pool so all literals that are the "same" point to the same instance and are never deallocated.
NSString *a = #"A";
NSString *b = #"A";
points to the same instance of a string, while:
NSString *a = [NSString stringWithFormat:#"A"];
NSString *b = [NSString stringWithFormat:#"A"];
are two different objects.
So it works when you are creating a literal array but when you add the blocks dynamically they will be removed when its time to use them therefor the BAD_ACCESS. Solution is to send "copy" message to the block that will copy it to the heap and the block will not be released.
It´s the same, you must have another kind of problem (sintax?).
Try this:
#interface ViewController ()
#property (nonatomic, strong) NSString *object1;
#property (nonatomic, strong) NSString *object2;
#property (nonatomic, strong) NSString *object3;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.object1 = #"object 1";
self.object2 = #"object 2";
self.object3 = #"object 3";
NSArray *actions = #[^() { [self showObject:self.object1];},
^() { [self showObject:self.object2]; },
^() {[self showObject:self.object3]; }
];
NSArray *secondActions = [[NSArray alloc] initWithObjects:
^() { [self showObject:self.object1];},
^() { [self showObject:self.object2]; },
^() { [self showObject:self.object3];},
nil
];
void(^firsSimpleBlock)(void) = [actions lastObject];
firsSimpleBlock();
void(^simpleBlock)(void) = [secondActions firstObject];
simpleBlock();
}
-(void)showObject:(NSString *)object
{
NSLog(#"Show: %#",object);
}
#end
In creating a login screen with static logins I'm trying to store them privately in the following class implementation. When a button creates IONServer objects I initialize it with the function -(void)login:(NSString *)username password:(NSString *)pw and pass it two UITextField.text strings.
If you notice in the init I am testing stuff with NSLog but at every breakpoint it seems like the storedLogins NSMutable array is nil.
IONServer.m
#import "IONServer.h"
#import "IONLoginResult.h"
#interface IONServer ()
#property (nonatomic) NSMutableArray *storedLogins;
#end
#implementation IONServer
-(void)createStoredLogins
{
NSArray *firstUser = #[#"user1",#"pass1"];
NSArray *secondUser = #[#"user2",#"pass2"];
[self.storedLogins addObject:firstUser];
[self.storedLogins addObject:secondUser];
}
-(instancetype)init {
self = [super init];
if (self) {
[self createStoredLogins];
NSLog(#"Stored logins: %#", _storedLogins);
NSLog(#"Stored user: %#", _storedLogins[0][0]);
}
return self;
}
-(void)login:(NSString *)username password:(NSString *)pw
{
NSArray *logins = [[NSArray alloc]initWithArray:_storedLogins];
for (int i = 0; i < [logins count]; i++) {
if (username == logins[i][0] && pw == logins[i][1]) {
IONLoginResult *result = [[IONLoginResult alloc] initWithResult:YES errorMessage:#"Success!"];
self.result = result;
break;
} else {
IONLoginResult *result = [[IONLoginResult alloc] initWithResult:NO errorMessage:#"Error!"];
self.result = result;
}
}
}
-(void)logout
{
}
#end
You need to initialize the array:
-(instancetype)init {
self = [super init];
if (self) {
_storedLogins = [[NSMutableArray alloc] init];
[self createStoredLogins];
NSLog(#"Stored logins: %#", _storedLogins);
NSLog(#"Stored user: %#", _storedLogins[0][0]);
}
return self;
}
So I currently have a functioning way of saving settings for my Cocos2d game, and the method I am using is an XML parser.
First of all, is it better to use NSUserDefaults for something like this?
EDIT: I DO APPOLOGIZE FOR THE LONG POST, IF YOU ALREADY KNOW HOW AN XML PARSER WORKS JUST SKIP TO THE END.
Here is my GlobalSettings.h:
#import <Foundation/Foundation.h>
#interface GlobalSettings : NSObject {
// Declare variables with an underscore in front, for example:
int _currency;
BOOL _BankerBossDefeated;
BOOL _BabyBossDefeated;
BOOL _DuckBossDefeated;
BOOL _BaseBallBossDefeated;
BOOL _NewtonBossDefeated;
BOOL _CatchExtender;
BOOL _CatchExtenderEnabled;
BOOL _SpeedBoost;
BOOL _SpeedBoostEnabled;
}
// Declare your variable properties without an underscore, for example:
#property (nonatomic, assign) int currency;
#property (nonatomic, assign) BOOL BankerBossDefeated;
#property (nonatomic, assign) BOOL BabyBossDefeated;
#property (nonatomic, assign) BOOL DuckBossDefeated;
#property (nonatomic, assign) BOOL BaseBallBossDefeated;
#property (nonatomic, assign) BOOL NewtonBossDefeated;
#property (nonatomic, assign) BOOL SpeedBoost;
#property (nonatomic, assign) BOOL CatchExtender;
#property (nonatomic, assign) BOOL SpeedBoostEnabled;
#property (nonatomic, assign) BOOL CatchExtenderEnabled;
// Put your custom init method interface here:
-(id)initWithcurrency:(int)currency
BankerBossDefeated:(BOOL)BankerBossDefeated
BabyBossDefeated:(BOOL)BabyBossDefeated
DuckBossDefeated:(BOOL)DuckBossDefeated
BaseBallBossDefeated:(BOOL)BaseBallBossDefeated
NewtonBossDefeated:(BOOL)NewtonBossDefeated
CatchExtender:(BOOL)CatchExtender
SpeedBoost:(BOOL)SpeedBoost
CatchExtenderEnabled:(BOOL)CatchExtenderEnabled
SpeedBoostEnabled:(BOOL)SpeedBoostEnabled;
#end
My GlobalSettings.m is:
#import "GlobalSettings.h"
#implementation GlobalSettings
// Synthesize your variables here, for example:
#synthesize currency = _currency;
#synthesize BankerBossDefeated = _BankerBossDefeated;
#synthesize BabyBossDefeated = _BabyBossDefeated;
#synthesize DuckBossDefeated = _DuckBossDefeated;
#synthesize BaseBallBossDefeated = _BaseBallBossDefeated;
#synthesize NewtonBossDefeated = _NewtonBossDefeated;
#synthesize SpeedBoost = _SpeedBoost;
#synthesize CatchExtender = _CatchExtender;
#synthesize SpeedBoostEnabled = _SpeedBoostEnabled;
#synthesize CatchExtenderEnabled = _CatchExtenderEnabled;
// put your custom init method here which takes a variable
// for each class instance variable
-(id)initWithcurrency:(int)currency
BankerBossDefeated:(BOOL)BankerBossDefeated
BabyBossDefeated:(BOOL)BabyBossDefeated
DuckBossDefeated:(BOOL)DuckBossDefeated
BaseBallBossDefeated:(BOOL)BaseBallBossDefeated
NewtonBossDefeated:(BOOL)NewtonBossDefeated
CatchExtender:(BOOL)CatchExtender
SpeedBoost:(BOOL)SpeedBoost
CatchExtenderEnabled:(BOOL)CatchExtenderEnabled
SpeedBoostEnabled:(BOOL)SpeedBoostEnabled;{
if ((self = [super init])) {
// Set class instance variables based on values
// given to this method
self.currency = currency;
self.BankerBossDefeated = BankerBossDefeated;
self.BabyBossDefeated = BabyBossDefeated;
self.DuckBossDefeated = DuckBossDefeated;
self.BaseBallBossDefeated = BaseBallBossDefeated;
self.NewtonBossDefeated = NewtonBossDefeated;
self.CatchExtender = CatchExtender;
self.SpeedBoost = SpeedBoost;
self.CatchExtenderEnabled = CatchExtenderEnabled;
self.SpeedBoostEnabled = SpeedBoostEnabled;
}
return self;
}
- (void) dealloc {
[super dealloc];
}
#end
I then parse the XML with SettingsParser.h:
#import <Foundation/Foundation.h>
#class GlobalSettings;
#interface SettingsParser : NSObject {}
+ (GlobalSettings *)loadData;
+ (void)saveData:(GlobalSettings *)saveData;
#end
and SettingsParser.m:
#import "SettingsParser.h"
#import "GlobalSettings.h"
#import "GDataXMLNode.h"
#implementation SettingsParser
+ (NSString *)dataFilePath:(BOOL)forSave {
NSString *xmlFileName = #"GlobalSettings";
/***************************************************************************
This method is used to set up the specified xml for reading/writing.
Specify the name of the XML file you want to work with above.
You don't have to worry about the rest of the code in this method.
***************************************************************************/
NSString *xmlFileNameWithExtension = [NSString stringWithFormat:#"%#.xml",xmlFileName];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *documentsPath = [documentsDirectory stringByAppendingPathComponent:xmlFileNameWithExtension];
if (forSave || [[NSFileManager defaultManager] fileExistsAtPath:documentsPath]) {
return documentsPath;
NSLog(#"%# opened for read/write",documentsPath);
} else {
NSLog(#"Created/copied in default %#",xmlFileNameWithExtension);
return [[NSBundle mainBundle] pathForResource:xmlFileName ofType:#"xml"];
}
}
+ (GlobalSettings *)loadData {
/***************************************************************************
This loadData method is used to load data from the xml file
specified in the dataFilePath method above.
MODIFY the list of variables below which will be used to create
and return an instance of TemplateData at the end of this method.
***************************************************************************/
int currency;
BOOL BankerBossDefeated;
BOOL BabyBossDefeated;
BOOL DuckBossDefeated;
BOOL BaseBallBossDefeated;
BOOL NewtonBossDefeated;
BOOL CatchExtender;
BOOL SpeedBoost;
BOOL CatchExtenderEnabled;
BOOL SpeedBoostEnabled;
// Create NSData instance from xml in filePath
NSString *filePath = [self dataFilePath:FALSE];
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
if (doc == nil) { return nil; NSLog(#"xml file is empty!");}
NSLog(#"Loading %#", filePath);
/***************************************************************************
This next line will usually have the most customisation applied because
it will be a direct reflection of what you want out of the XML file.
***************************************************************************/
NSArray *dataArray = [doc nodesForXPath:#"//GlobalSettings" error:nil];
NSLog(#"Array Contents = %#", dataArray);
/***************************************************************************
We use dataArray to populate variables created at the start of this
method. For each variable you will need to:
1. Create an array based on the elements in the xml
2. Assign the variable a value based on data in elements in the xml
***************************************************************************/
for (GDataXMLElement *element in dataArray) {
NSArray *currencyArray = [element elementsForName:#"currency"];
NSArray *BankerBossDefeatedArray = [element elementsForName:#"BankerBossDefeated"];
NSArray *BabyBossDefeatedArray = [element elementsForName:#"BabyBossDefeated"];
NSArray *DuckBossDefeatedArray = [element elementsForName:#"DuckBossDefeated"];
NSArray *BaseBallBossDefeatedArray = [element elementsForName:#"BaseBallBossDefeated"];
NSArray *NewtonBossDefeatedArray = [element elementsForName:#"NewtonBossDefeated"];
NSArray *CatchExtenderArray = [element elementsForName:#"CatchExtender"];
NSArray *SpeedBoostArray = [element elementsForName:#"SpeedBoost"];
NSArray *CatchExtenderEnabledArray = [element elementsForName:#"CatchExtenderEnabled"];
NSArray *SpeedBoostEnabledArray = [element elementsForName:#"SpeedBoostEnabled"];
// currency
if (currencyArray.count > 0) {
GDataXMLElement *currencyElement = (GDataXMLElement *) [currencyArray objectAtIndex:0];
currency = [[currencyElement stringValue] intValue];
}
// BankerBossDefeated
if (BankerBossDefeatedArray.count > 0) {
GDataXMLElement *BankerBossDefeatedElement = (GDataXMLElement *) [BankerBossDefeatedArray objectAtIndex:0];
BankerBossDefeated = [[BankerBossDefeatedElement stringValue] boolValue];
}
// DuckBossDefeated
if (DuckBossDefeatedArray.count > 0) {
GDataXMLElement *DuckBossDefeatedElement = (GDataXMLElement *) [DuckBossDefeatedArray objectAtIndex:0];
DuckBossDefeated = [[DuckBossDefeatedElement stringValue] boolValue];
}
// BabyBossDefeated
if (BabyBossDefeatedArray.count > 0) {
GDataXMLElement *BabyBossDefeatedElement = (GDataXMLElement *) [BabyBossDefeatedArray objectAtIndex:0];
BabyBossDefeated = [[BabyBossDefeatedElement stringValue] boolValue];
}
// BaseBallBossDefeated
if (BaseBallBossDefeatedArray.count > 0) {
GDataXMLElement *BaseBallBossDefeatedElement = (GDataXMLElement *) [BaseBallBossDefeatedArray objectAtIndex:0];
BaseBallBossDefeated = [[BaseBallBossDefeatedElement stringValue] boolValue];
}
// NewtonBossDefeated
if (NewtonBossDefeatedArray.count > 0) {
GDataXMLElement *NewtonBossDefeatedElement = (GDataXMLElement *) [NewtonBossDefeatedArray objectAtIndex:0];
NewtonBossDefeated = [[NewtonBossDefeatedElement stringValue] boolValue];
}
// CatchExtender
if (CatchExtenderArray.count > 0) {
GDataXMLElement *CatchExtenderElement = (GDataXMLElement *) [CatchExtenderArray objectAtIndex:0];
CatchExtender = [[CatchExtenderElement stringValue] boolValue];
}
// SpeedBoost
if (SpeedBoostArray.count > 0) {
GDataXMLElement *SpeedBoostElement = (GDataXMLElement *) [SpeedBoostArray objectAtIndex:0];
SpeedBoost = [[SpeedBoostElement stringValue] boolValue];
}
// CatchExtenderEnabled
if (CatchExtenderEnabledArray.count > 0) {
GDataXMLElement *CatchExtenderEnabledElement = (GDataXMLElement *) [CatchExtenderEnabledArray objectAtIndex:0];
CatchExtenderEnabled = [[CatchExtenderEnabledElement stringValue] boolValue];
}
// SpeedBoost
if (SpeedBoostEnabledArray.count > 0) {
GDataXMLElement *SpeedBoostEnabledElement = (GDataXMLElement *) [SpeedBoostEnabledArray objectAtIndex:0];
SpeedBoostEnabled = [[SpeedBoostEnabledElement stringValue] boolValue];
}
}
/***************************************************************************
Now the variables are populated from xml data we create an instance of
TemplateData to pass back to whatever called this method.
The initWithExampleInt:exampleBool:exampleString will need to be replaced
with whatever method you have updaed in the TemplateData class.
***************************************************************************/
//NSLog(#"XML value read for exampleInt = %i", exampleInt);
//NSLog(#"XML value read for exampleBool = %i", exampleBool);
//NSLog(#"XML value read for exampleString = %#", exampleString);
GlobalSettings *Data = [[GlobalSettings alloc] initWithcurrency:currency
BankerBossDefeated:BankerBossDefeated
BabyBossDefeated:BabyBossDefeated DuckBossDefeated:DuckBossDefeated BaseBallBossDefeated:BaseBallBossDefeated NewtonBossDefeated:NewtonBossDefeated
CatchExtender:CatchExtender
SpeedBoost:SpeedBoost CatchExtenderEnabled:CatchExtenderEnabled
SpeedBoostEnabled:SpeedBoostEnabled];
[doc release];
[xmlData release];
return Data;
[Data release];
}
+ (void)saveData:(GlobalSettings *)saveData {
/***************************************************************************
This method writes data to the xml based on a TemplateData instance
You will have to be very aware of the intended xml contents and structure
as you will be wiping and re-writing the whole xml file.
We write an xml by creating elements and adding 'children' to them.
You'll need to write a line for each element to build the hierarchy // <-- MODIFY CODE ACCORDINGLY
***************************************************************************/
GDataXMLElement *GlobalSettingsElement = [GDataXMLNode elementWithName:#"GlobalSettings"];
GDataXMLElement *currencyElement = [GDataXMLNode elementWithName:#"currency"
stringValue:[[NSNumber numberWithInt:saveData.currency] stringValue]];
GDataXMLElement *BankerBossDefeatedElement = [GDataXMLNode elementWithName:#"BankerBossDefeated"
stringValue:[[NSNumber numberWithBool:saveData.BankerBossDefeated] stringValue]];
GDataXMLElement *BabyBossDefeatedElement = [GDataXMLNode elementWithName:#"BabyBossDefeated"
stringValue:[[NSNumber numberWithBool:saveData.BabyBossDefeated] stringValue]];
GDataXMLElement *DuckBossDefeatedElement = [GDataXMLNode elementWithName:#"DuckBossDefeated"
stringValue:[[NSNumber numberWithBool:saveData.DuckBossDefeated] stringValue]];
GDataXMLElement *BaseBallBossDefeatedElement = [GDataXMLNode elementWithName:#"BaseBallBossDefeated"
stringValue:[[NSNumber numberWithBool:saveData.BaseBallBossDefeated] stringValue]];
GDataXMLElement *NewtonBossDefeatedElement = [GDataXMLNode elementWithName:#"NewtonBossDefeated"
stringValue:[[NSNumber numberWithBool:saveData.NewtonBossDefeated] stringValue]];
GDataXMLElement *CatchExtenderElement = [GDataXMLNode elementWithName:#"CatchExtender"
stringValue:[[NSNumber numberWithBool:saveData.CatchExtender] stringValue]];
GDataXMLElement *SpeedBoostElement = [GDataXMLNode elementWithName:#"SpeedBoost"
stringValue:[[NSNumber numberWithBool:saveData.SpeedBoost] stringValue]];
GDataXMLElement *CatchExtenderEnabledElement = [GDataXMLNode elementWithName:#"CatchExtenderEnabled"
stringValue:[[NSNumber numberWithBool:saveData.CatchExtender] stringValue]];
GDataXMLElement *SpeedBoostEnabledElement = [GDataXMLNode elementWithName:#"SpeedBoostEnabled"
stringValue:[[NSNumber numberWithBool:saveData.SpeedBoost] stringValue]];
// Using the elements just created, set up the hierarchy
[GlobalSettingsElement addChild:currencyElement];
[GlobalSettingsElement addChild:BankerBossDefeatedElement];
[GlobalSettingsElement addChild:BabyBossDefeatedElement];
[GlobalSettingsElement addChild:DuckBossDefeatedElement];
[GlobalSettingsElement addChild:BaseBallBossDefeatedElement];
[GlobalSettingsElement addChild:NewtonBossDefeatedElement];
[GlobalSettingsElement addChild:CatchExtenderElement];
[GlobalSettingsElement addChild:SpeedBoostElement];
[GlobalSettingsElement addChild:CatchExtenderEnabledElement];
[GlobalSettingsElement addChild:SpeedBoostEnabledElement];
GDataXMLDocument *document = [[[GDataXMLDocument alloc]
initWithRootElement:GlobalSettingsElement] autorelease];
NSData *xmlData = document.XMLData;
NSString *filePath = [self dataFilePath:TRUE];
NSLog(#"Saving data to %#...", filePath);
[xmlData writeToFile:filePath atomically:YES];
}
#end
EDIT: THE ACTUAL PROBLEM BEGINS HERE:
In my menu class I have two switches that come up when CatchExtender and SpeedBoost become enabled (they are purchased in the game's store). In those switches, I want to set SpeedBoostEnabled and CatchExtenderEnabled depending on the switch.
These are the switches:
IN MY INIT:
if (GlobalSettings.CatchExtender == TRUE) {
if(GlobalSettings.SpeedBoost == TRUE){
catchSwitch = [[ UISwitch alloc ] initWithFrame: CGRectMake(220, 400, 10, 10)];
catchSwitch.center = CGPointMake(240, 450);
CCLabelTTF *catchLabel = [CCLabelTTF labelWithString:#"Catch Extender" fontName:#"Chalkduster" fontSize:15];
catchLabel.color = ccWHITE;
catchLabel.position = CGPointMake(240, 60);
[self addChild: catchLabel];
}else{
catchSwitch = [[ UISwitch alloc ] initWithFrame: CGRectMake(160, 400, 10, 10)];
catchSwitch.center = CGPointMake(160, 450);
CCLabelTTF *catchLabel = [CCLabelTTF labelWithString:#"Catch Extender" fontName:#"Chalkduster" fontSize:15];
catchLabel.color = ccWHITE;
catchLabel.position = CGPointMake(160, 60);
[self addChild: catchLabel];
}
catchSwitch.on = NO; //set to be OFF at start
catchSwitch.tag = 1; // this is not necessary - only to find later
[catchSwitch addTarget:self action:#selector(catchAction:) forControlEvents:UIControlEventValueChanged];
[[[CCDirector sharedDirector] openGLView] addSubview:catchSwitch];
}
if (GlobalSettings.SpeedBoost == TRUE) {
if(GlobalSettings.CatchExtender == TRUE){
speedSwitch = [[ UISwitch alloc ] initWithFrame: CGRectMake(100, 400, 10, 10)];
speedSwitch.center = CGPointMake(80, 450);
CCLabelTTF *speedLabel = [CCLabelTTF labelWithString:#"Speed Enhancer" fontName:#"Chalkduster" fontSize:15];
speedLabel.color = ccWHITE;
speedLabel.position = CGPointMake(80, 60);
[self addChild: speedLabel];
}else{
speedSwitch = [[ UISwitch alloc ] initWithFrame: CGRectMake(160, 400, 10, 10)];
speedSwitch.center = CGPointMake(160, 450);
}
speedSwitch.on = NO; //set to be OFF at start
speedSwitch.tag = 1; // this is not necessary - only to find later
[speedSwitch addTarget:self action:#selector(speedAction:) forControlEvents:UIControlEventValueChanged];
[[[CCDirector sharedDirector] openGLView] addSubview:speedSwitch];
}
IN THE ACTIONS:
- (void)catchAction:(id)sender
{
// Your logic when the switch it used
// NSLog(#"switchAction: value = %d", [sender isOn]);
if ([sender isOn]) {
GlobalSettings *GlobalSettings = [SettingsParser loadData];
GlobalSettings.CatchExtenderEnabled = TRUE;
UIAlertView* dialog = [[UIAlertView alloc] init];
[dialog setDelegate:self];
[dialog setTitle:#"ON"];
[dialog setMessage:#"Catch Extender is on"];
[dialog addButtonWithTitle:#"Sweet!"];
[dialog show];
[dialog release];
[SettingsParser saveData:GlobalSettings];
}else{
GlobalSettings *GlobalSettings = [SettingsParser loadData];
GlobalSettings.CatchExtenderEnabled = FALSE;
UIAlertView* dialog = [[UIAlertView alloc] init];
[dialog setDelegate:self];
[dialog setTitle:#"OFF"];
[dialog setMessage:#"Catch Extender is off"];
[dialog addButtonWithTitle:#"Thanks"];
[dialog show];
[dialog release];
[SettingsParser saveData:GlobalSettings];
}
}
- (void)speedAction:(id)sender
{
// Your logic when the switch it used
// NSLog(#"switchAction: value = %d", [sender isOn]);
if ([sender isOn]) {
GlobalSettings *GlobalSettings = [SettingsParser loadData];
GlobalSettings.SpeedBoostEnabled = TRUE;
UIAlertView* dialog = [[UIAlertView alloc] init];
[dialog setDelegate:self];
[dialog setTitle:#"ON"];
[dialog setMessage:#"Speed Enhancer is on"];
[dialog addButtonWithTitle:#"Sweet!"];
[dialog show];
[dialog release];
[SettingsParser saveData:GlobalSettings];
}else{
GlobalSettings *GlobalSettings = [SettingsParser loadData];
GlobalSettings.SpeedBoostEnabled = FALSE;
UIAlertView* dialog = [[UIAlertView alloc] init];
[dialog setDelegate:self];
[dialog setTitle:#"OFF"];
[dialog setMessage:#"Speed Enhancer is off"];
[dialog addButtonWithTitle:#"Thanks"];
[dialog show];
[dialog release];
[SettingsParser saveData:GlobalSettings];
}
}
In the classes that hold the data for the game levels, I check these Booleans normally with a simple if statement. But it doesn't seem to work, the settings don't seem to save because in the Log it doesn't seem like the values have changed in the XML file..
Again sorry for the long post but this issue is kinda bugging me.
I've never used Cocos2D before but this seems like exactly what you would use NSUserDefaults for in a typical iOS app, like this: [[NSUserDefaults standardUserDefaults] setBool:YES ForKey:#"Key"];