I want to use Mantle framework (https://github.com/github/Mantle) to support NSCoding for my class with struct property:
typedef struct {
int x;
int y;
} MPoint;
typedef struct {
MPoint min;
MPoint max;
} MRect;
#interface MObject : MTLModel
#property (assign, nonatomic) MRect rect;
#end
#implementation MObject
#end
But when I tried to [NSKeyedArchiver archiveRootObject:obj toFile:#"file"]; its crashed in MTLModel+NSCoding.m, in - (void)encodeWithCoder:(NSCoder *)coder on line
case MTLModelEncodingBehaviorUnconditional:
[coder encodeObject:value forKey:key];
Does Mantle supports c-struct encoding (and also decoding) or I've need to custom implementing NSCoding protocol for such classes?
My original data structure is an XML (yeah, I know):
...
<Lat>32.062883</Lat>
<Lot>34.782904</Lot>
...
I used MTLXMLAdapter based on KissXML, but you can see how it's applicable to any other serializer.
+ (NSValueTransformer *)coordinateXMLTransformer {
return [MTLValueTransformer reversibleTransformerWithBlock:^id(NSArray *nodes) {
CLLocationCoordinate2D coordinate;
for (DDXMLNode *node in nodes) {
if ([[node name] isEqualToString:#"Lat"]) {
coordinate.latitude = [[node stringValue] doubleValue];
} else if ([[node name] isEqualToString:#"Lot"]) {
coordinate.longitude = [[node stringValue] doubleValue];
}
}
return [NSValue value:&coordinate
withObjCType:#encode(CLLocationCoordinate2D)];
}];
}
You can add a reverseBlock if needed.
It was easier than I thought:
Exclude property in +encodingBehaviorsByPropertyKey
Manual encode/encode excluded property
Sample:
#pragma mark - MTLModel + NSCoding
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
self.rect = [[self class] mRectFromData:[coder decodeObjectForKey:#"rectData"]];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:[[self class] dataFromMRect:self.rect] forKey:#"rectData"];
}
+ (NSDictionary *)encodingBehaviorsByPropertyKey {
NSDictionary *excludeProperties = #{
NSStringFromSelector(#selector(rect)): #(MTLModelEncodingBehaviorExcluded)
};
NSDictionary *encodingBehaviors = [[super encodingBehaviorsByPropertyKey] mtl_dictionaryByAddingEntriesFromDictionary:excludeProperties];
return encodingBehaviors;
}
#pragma mark - MRect transformations
+ (MRect)mRectFromData:(NSData *)rectData {
MRect rect;
[rectData getBytes:&rect length:sizeof(rect)];
return rect;
}
+ (NSData *)dataFromMRect:(MRect)rect {
return [NSData dataWithBytes:&rect length:sizeof(rect)];
}
Related
I simplify the code for ask question.
I want to save more class and load class model file.
But now I simplify parameters to one part (lim) in the model(FlightSettingModel.h).
In the FlightSettingModel.h I have set some parameters in here.
FlightSettingModel.h code below:
#import <Foundation/Foundation.h>
#interface FlightSettingModel : NSObject<NSCoding>
// lim property part
#property float limCurrentVal;
#property float limDefaultVal;
#property float limMaxVal;
#property float limMinVal;
// .... other property part
#end
FlightSettingModel.m code below
#import "FlightSettingModel.h"
#interface FlightSettingModel()
{
}
#end
#implementation FlightSettingModel
-(instancetype)init
{
self = [super init];
if(self)
{
self.limDefaultVal = 3.0;
self.limCurrentVal = 4.0;
self.limMaxVal = 10;
self.limMinVal = 0;
// ... other property part value init .....
}
return self;
}
- (void)setFlightSettingToDefaultValue
{
self.limCurrentVal = self.limDefaultVal;
}
- (void) encodeWithCoder: (NSCoder *)encoder
{
[encoder encodeFloat:self.limCurrentVal forKey:#"limCurrentVal"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.limCurrentVal = [decoder decodeFloatForKey:#"limCurrentVal"];
}
return self;
}
#end
Then I have set the singleton SettingData file to initial the FlightSettingModel and other model class.
The SettingData model header like below:
#import <Foundation/Foundation.h>
#import "FlightSettingModel.h"
#interface SettingData : NSObject
#property (nonatomic,strong) FlightSettingModel *flightSettingModel;
+(SettingData*) sharedInstance;
#end
SettingData.m code below:
#import "SettingData.h"
#implementation SettingData
SettingData *sharedInstance;
+(SettingData*) sharedInstance{
if( sharedInstance == nil )
{
sharedInstance = [SettingData new];
}
return sharedInstance;
}
-(id) init{
self = [super init];
if( self )
{
self.flightSettingModel = [FlightSettingModel new];
}
return self;
}
#end
In my storyboard (UI) is like below:
When I click the save button , I want to save the custom class model(FlightSettingModel.h) in the NSKeyedArchiver. When I click the load, I want to load the model from the archiver using NSKeyedUnarchiver and resetting to the slider.
But now,when I drag the slider to other value(ex:10), then I click the save, then I close the app restart the app. I click the load, the slider value will become 0.
I don't know why when I load the value the all value will become 0;
My view controller code .m below:
#import "ViewController.h"
#import "SettingData.h"
#interface ViewController ()
{
NSString *path;
NSString *fileName;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// file manage
path =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
fileName = [path stringByAppendingPathComponent:#"flightFile"];
[self setUIValue];
NSLog(#"---");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (IBAction)saveAction:(UIButton *)sender {
[NSKeyedArchiver archiveRootObject:[SettingData sharedInstance].flightSettingModel toFile:fileName];
}
- (IBAction)loadAction:(UIButton *)sender {
[SettingData sharedInstance].flightSettingModel = (FlightSettingModel*) [NSKeyedUnarchiver unarchiveObjectWithFile: fileName];
[self setUIValue];
NSLog(#"current Value:%.f",[SettingData sharedInstance].flightSettingModel.limCurrentVal);
}
- (IBAction)sliderChangedAction:(UISlider *)sender {
[SettingData sharedInstance].flightSettingModel.limCurrentVal = sender.value;
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
-(void) setUIValue
{
// setting slider property
self.theSlider.maximumValue = [SettingData sharedInstance].flightSettingModel.limMaxVal;
self.theSlider.minimumValue = [SettingData sharedInstance].flightSettingModel.limMinVal;
self.theSlider.value = [SettingData sharedInstance].flightSettingModel.limCurrentVal;
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
#end
Have anyone know where my problem in my code?
thank you very much.
If you want to download the complete code(the question code) , I have upload in github
This behavior happens because the [SettingData sharedInstance].flightSettingModel.limMaxVal and the [SettingData sharedInstance].flightSettingModel.limMinVal are zero:
-(void) setUIValue
{
// setting slider property
self.theSlider.maximumValue = [SettingData sharedInstance].flightSettingModel.limMaxVal;
// self.theSlider.maximumValue = 0
self.theSlider.minimumValue = [SettingData sharedInstance].flightSettingModel.limMinVal;
// self.theSlider.minimumValue = 0
self.theSlider.value = [SettingData sharedInstance].flightSettingModel.limCurrentVal;
// [SettingData sharedInstance].flightSettingModel.limCurrentVal = 10
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
EDIT: You can fix it by adding this:
- (void) encodeWithCoder: (NSCoder *)encoder {
[encoder encodeFloat:self.limCurrentVal forKey:#"limCurrentVal"];
[encoder encodeFloat:self.limMaxVal forKey:#"limMaxVal"];
[encoder encodeFloat:self.limMinVal forKey:#"limMinVal"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.limCurrentVal = [decoder decodeFloatForKey:#"limCurrentVal"];
self.limMaxVal = [decoder decodeFloatForKey:#"limMaxVal"];
self.limMinVal = [decoder decodeFloatForKey:#"limMinVal"];
}
return self;
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
The Goal and Problem:
After every passing second, the value of 'regularBubbleCount' - a variable of RWGameData - increases by 1. I am attempting to display this change in value by passing the new value of 'regularBubbleCount' to the 'regularBubLabel' UILabel in the PrimaryViewController. I am attempting to do this by using the following line of code,
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
Obviously this does not work because 'regularBubLabel' is not an object of the RWGameData class where the 'timerCalled' method resides. How can I change the value of the 'regularBubLabel' from inside the RWGameData class?
RWGameData.h
#import <Foundation/Foundation.h>
#interface RWGameData : NSObject <NSCoding>
#property (assign, nonatomic) long regularBubbleCount;
#property (assign, nonatomic) BOOL dataIsInitialized;
+(instancetype)sharedGameData;
-(void)reset;
-(void)save;
-(void)timerSetup;
-(void)timerCalled;
#end
RWGameData.m
#import "RWGameData.h"
#implementation RWGameData
static NSString* const SSGameDataRegularBubbleCountKey = #"regularBubbleCount";
static NSString* const SSGameDataIsInitializedKey = #"dataIsInitializedKey";
+ (instancetype)sharedGameData {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self loadInstance];
});
return sharedInstance;
}
-(void)reset {
self.regularBubbleCount = 0;
self.dataIsInitialized = true;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeDouble:self.regularBubbleCount forKey: SSGameDataRegularBubbleCountKey];
[encoder encodeBool:self.dataIsInitialized forKey: SSGameDataIsInitializedKey];
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_regularBubbleCount = [decoder decodeDoubleForKey: SSGameDataRegularBubbleCountKey];
_dataIsInitialized = [decoder decodeBoolForKey: SSGameDataIsInitializedKey];
}
return self;
}
+(NSString*)filePath
{
static NSString* filePath = nil;
if (!filePath) {
filePath =
[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
stringByAppendingPathComponent:#"gamedata"];
}
return filePath;
}
+(instancetype)loadInstance
{
NSData* decodedData = [NSData dataWithContentsOfFile: [RWGameData filePath]];
if (decodedData) {
RWGameData* gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
return [[RWGameData alloc] init];
}
-(void)save
{
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self];
[encodedData writeToFile:[RWGameData filePath] atomically:YES];
}
- (void)timerSetup { // to be called from delegate didFinishLaunching….
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerCalled) userInfo:nil repeats:YES];
}
-(void)timerCalled
{
[RWGameData sharedGameData].regularBubbleCount++;
/* THE ISSUE IS HERE */
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
[[RWGameData sharedGameData] save];
} NSLog(#"Regular Bubble Count: %li", [RWGameData sharedGameData].regularBubbleCount);
}
#end
PrimaryViewController.h
#import <UIKit/UIKit.h>
#import "RWGameData.h"
#interface PrimaryViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *regularBubLabel;
#end
PrimaryViewController.m
#import "PrimaryViewController.h"
#interface PrimaryViewController ()
#end
#implementation PrimaryViewController
{
NSString *bubbleImage;
UIImage *backgroundImage;
UIImageView *backgroundImageView;
int r;
int i;
}
- (void)viewDidLoad {
[super viewDidLoad];
backgroundImage = [UIImage imageNamed:#"background_new.png"];
backgroundImageView=[[UIImageView alloc]initWithFrame:self.view.frame];
backgroundImageView.image=backgroundImage;
[self.view insertSubview:backgroundImageView atIndex:0];
}
- (void)viewDidAppear:(BOOL)animated {
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
_premiumBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].premiumBubbleCount];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)increment {
if ([RWGameData sharedGameData].megaBubblePopValue == 0) {
[RWGameData sharedGameData].megaBubblePopValue++;
[[RWGameData sharedGameData] save];
}
if ([#"mysterybubble.png" isEqual:bubbleImage]) {
[RWGameData sharedGameData].premiumBubbleCount += 2;
_premiumBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].premiumBubbleCount];
} else if ([#"megaBubbleLarge30.png" isEqual:bubbleImage]) {
[RWGameData sharedGameData].regularBubbleCount += [RWGameData sharedGameData].megaBubblePopValue;
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
} i++;
}
- (IBAction)save {
[[RWGameData sharedGameData] save];
}
- (IBAction)setBubbleStatus {
r = arc4random_uniform(400);
if (r <= 1) {
bubbleImage = #"mysterybubble.png";
[_megaBubbleButton setImage:[UIImage imageNamed:bubbleImage] forState:UIControlStateNormal];
NSLog(#"Roll SUCCESS. [%i] %i", i, r);
} else {
bubbleImage = #"megaBubbleLarge30.png";
[_megaBubbleButton setImage:[UIImage imageNamed:bubbleImage] forState:UIControlStateNormal];
NSLog(#"Roll FAIL. [%i] %i", i, r);
}
}
#end
It makes no sense to have an IBOutlet in your RWGameData class, since it's a subclass of NSObject. That class has no instance (and no view) in the storyboard, so you can't hook up an IBOutlet. One way to accomplish your goal would be to make PrimaryViewController a delegate of RWGameData, and call a delegate method inside the timer's action method that would pass whatever data you need so PrimaryViewController can update its own label.
I am going to make the following assumptions:
Assumption #1 -RWGameData is instantiated inside the ApplicationDelegate when the application is loaded and can be referenced in the application delegate as self.gameData
Assumption #2 - PrimaryViewController is, as you have named it, the primary view controller in your storyboard.
Assumption #3 - You're using an NSTimer to update this count every 1 second.
Assumption #4 - Your Storyboard file is called Mainstorybaord
RWGameData
Add this to your RWGameData.h before your class definition.
#class RWGameData;
#protocol RWGameStateProtocol <NSObject>
-(void)StateUpdateForGameData:(RWGameData*)data;
#end
Inside RWGameData.h add a delegate in your class definition.
#interface RWGameData : NSObject
#property(weak)id<RWGameStateProtocol> delegate;
#end
In your selector timerCalled add a call to your delegate informing it the data has changed.
-(void)timerCalled
{
[RWGameData sharedGameData].regularBubbleCount++;
/* THE ISSUE IS HERE */
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
[[RWGameData sharedGameData] save];
} NSLog(#"Regular Bubble Count: %li", [RWGameData sharedGameData].regularBubbleCount);
/* Inform the delegate */
[self.delegate StateUpdateForGameData:self];
}
PrimaryViewController
Once you have your protocol defined have your PrimaryViewcontroller conform to this protocol with something similar to the following.
-(void)StateUpateforGameData:(RWGameData*)data
{
self.regularBubLabel.text = [[NSString alloc]initWithFormat:#"%i",data.regularBubbleCount];
}
Application Delegate
All that remains is to set the delegate on your RWGameData instance. You can do this by asking storyboard to provide you an instance of your primary view controller. If you're using storyboard then by default this method is empty. However, since we are going to modify the primary view controller before it is shown then we need to make a few changes and perform some of the work Storyboard already does for you.
In your ApplicationDelegate's header file create a property for a UIWindow and RWGameData.
#interface YourAppDelegate : UIResponder <UIApplicationDelegate>
#property(strong,nonatomic) UIWindow *window;
#property(strong,nonatomic) RWGameData *gameData;
#end
Now that we have the properties setup you need to perform some additional work. Note that normally you don't have to perform this step when using storyboard.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
/// Screen Information
CGFloat scale = [[UIScreen mainScreen]scale];
CGRect frame = [[UIScreen mainScreen]bounds];
UIWindow* window = [[UIWindow alloc]initWithFrame:frame];
self.window = window;
self.window.contentScaleFactor = scale;
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
PrimaryViewController *vc = (PrimaryViewController*)[sb instantiateInitialViewController];
RWGameData *gameData = [[RWGameData alloc]init];
gameData.delegate = vc;
self.gameData = gameData;
[window setRootViewController:vc];
[window makeKeyAndVisible];
return YES;
}
This approach doesn't require you to tightly couple your label and game data. Instead any update to your game data can now inform your view and allows your view to select which labels need to be updated.
I use this code to get the path of a not existing file in documents directory of my application
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"restaurants.xml"];
getting this path:
/var/mobile/Containers/Data/Application/50BC3507-39BA-4F7A-86BA-254AB9DA6184/Documents/restaurants.xml
My question: How can I make my class being able to be saved in an array?
function to get path:
- (NSString *)restaurantListPath {
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"restaurants.xml"];
return path;
}
function to write array:
- (void)writeRestaurantListToFile:(NSMutableArray *)restaurantList {
[restaurantList writeToFile:[self restaurantListPath] atomically:YES];
}
function to load array:
- (NSMutableArray *)restaurantListOfFile {
NSMutableArray *restaurantList = [NSMutableArray arrayWithContentsOfFile:[self restaurantListPath]];
if (!restaurantList) {
restaurantList = [NSMutableArray array];
}
return restaurantList;
}
I created an array containing 1 JFRestaurant item and saved it with the function above. Then I loaded it with the function above and the array was empty (initialized but with no content).
If there would be an error while writing the file or reading the file the array would be nil not empty. So I think that the content of the array is the problem.
May my class JFRestaurant cannot be wrote to file. I looked at a tutorial and saw, that I need to implement NSCoding protocol to make my class being able to be written to file and did that, but the array is still empty. I also tried with NSKeyedArchiver and NSKeyedUnarchiver, but then I get another error
2014-08-25 16:52:23.969 Restaurants[5577:934385] -[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x170050860
2014-08-25 16:52:23.970 Restaurants[5577:934385] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x170050860'
*** First throw call stack:
(0x185866084 0x1954180e4 0x18586d094 0x185869e48 0x18576f08c 0x185741d98 0x18670c9e0 0x18676e170 0x10007d3a0 0x100077cdc 0x189eb39ec 0x189f510b8 0x189f50b0c 0x189f5082c 0x189f507ac 0x189e997a0 0x1897f22a4 0x1897ece90 0x1897ecd34 0x1897ec534 0x1897ec2b8 0x18a125b54 0x18a126a00 0x18a124b84 0x18d96c6ac 0x18581e360 0x18581d468 0x18581b668 0x185749664 0x189f07140 0x189f02164 0x10007b518 0x195a8ea08)
libc++abi.dylib: terminating with uncaught exception of type NSException
JFRestaurant.h:
//
// JFRestaurant.h
// Restaurants
//
// Created by Jonas Frey on 15.08.14.
// Copyright (c) 2014 Jonas Frey. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "JFUtils.h"
#import "JFItem.h"
#interface JFRestaurant : NSObject <NSCoding>
#property (strong, nonatomic) NSString *name;
#property float score;
- (void)addItem:(JFItem *)item;
- (void)removeItem:(JFItem *)item;
- (void)removeItemAtIndex:(NSUInteger)index;
- (void)setItem:(JFItem *)item atIndex:(NSUInteger)index;
- (JFItem *)itemAtIndex:(NSUInteger)index;
- (NSUInteger)indexOfItem:(JFItem *)item;
- (NSInteger)itemCount;
- (NSMutableArray *)fullItemArray;
- (id)initWithName:(NSString *)name;
- (id)initWithName:(NSString *)name items:(NSMutableArray *)items;
#end
JFRestaurant.m
//
// JFRestaurant.m
// Restaurants
//
// Created by Jonas Frey on 15.08.14.
// Copyright (c) 2014 Jonas Frey. All rights reserved.
//
#import "JFRestaurant.h"
#interface JFRestaurant ()
#property (strong, nonatomic) NSMutableArray *items;
- (float)scoreOfItems;
#end
#implementation JFRestaurant
- (id)initWithCoder:(NSCoder *)aDecoder {
NSString *name = [aDecoder decodeObjectForKey:JFKeyRestaurantCoderName];
NSMutableArray *items = [aDecoder decodeObjectForKey:JFKeyRestaurantCoderItems];
return [self initWithName:name items:items];
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_name forKey:JFKeyRestaurantCoderName];
[aCoder encodeObject:_items forKey:JFKeyRestaurantCoderItems];
}
- (id)init {
self = [super init];
if (self) {
self.name = #"Neues Restaurant";
self.items = [[[JFUtils alloc] init] defaultItems];
self.score = [self scoreOfItems];
}
return self;
}
- (id)initWithName:(NSString *)name {
self = [super init];
if (self) {
self.name = name;
self.items = [[[JFUtils alloc] init] defaultItems];
self.score = [self scoreOfItems];
}
return self;
}
- (id)initWithName:(NSString *)name items:(NSMutableArray *)items {
self = [super init];
if (self) {
self.name = name;
self.items = items;
self.score = [self scoreOfItems];
}
return self;
}
- (void)addItem:(JFItem *)item {
[self.items addObject:item];
self.score = [self scoreOfItems];
}
- (void)removeItem:(JFItem *)item {
[self.items removeObject:item];
self.score = [self scoreOfItems];
}
- (void)removeItemAtIndex:(NSUInteger)index {
[self.items removeObjectAtIndex:index];
self.score = [self scoreOfItems];
}
- (void)setItem:(JFItem *)item atIndex:(NSUInteger)index {
[self.items setObject:item atIndexedSubscript:index];
self.score = [self scoreOfItems];
}
- (JFItem *)itemAtIndex:(NSUInteger)index {
return [self.items objectAtIndex:index];
}
- (NSUInteger)indexOfItem:(JFItem *)item {
return [self.items indexOfObject:item];
}
- (NSInteger)itemCount {
return self.items.count;
}
- (NSMutableArray *)fullItemArray {
return self.items;
}
- (float)scoreOfItems {
float value = 0.0f;
float totalPossible = 0.0f;
for (JFItem *item in self.items) {
value += (4 - (float)item.segmentIndex) * 25.0f;
totalPossible += 100.0f;
}
return value / totalPossible * 100.0f;
}
#end
Thanks for your help
iComputerfreak
I've been exploring virtual card deck randomisation and drawing of cards as a little side hobby recently, and have been focussing on a great tutorial found at Ontswedder. However, I've run into a minor issue which I'm hoping you can help me with!
Below, I've included some key aspects of the code for your viewing.
In my viewController, I'm wanting to call both the shuffle and draw methods, found in Deck.m. This is possible if I do the following:
Deck *d = [[Deck alloc] init];
[d shuffle];
[d deal];
However, I need to be able to split these. For example, clicking one button initialises the deck, and shuffles it, whilst another new button draws a new card (until the deck is empty).
I am having issues with this, and I could do the following:
Deck *d = [[Deck alloc] init];
[d shuffle];
Deck *d = [[Deck alloc] init];
[d deal];
But this is useless to me, as if I call the init then a NEW, UNSHUFFLED deck is created and the card is dealt from this new deck.
I've tried declaring Deck *DeckObject in viewController.h, then simply calling [DeckObject draw];, however this provides a null value.
Ultimately, I would like to call these two methods separately. Without needing to init a new deck every time if possible!! :-)
I greatly appreciate both everyone's help on this post but also site-wide!
I hope this makes sense, too. Please let me know if you would like more information.
Also, I apologise if any of my terminology is incorrect - still a bit of a newbie!
Included code for context:
Card.h
typedef enum {
Hearts,
Diamonds,
Spades,
Clubs
} Suit;
#define Ace 1
#define Jack 11
#define Queen 12
#define King 13
#interface Card : NSObject {
NSInteger value;
Suit suit;
}
#property (nonatomic) NSInteger value;
#property (nonatomic) Suit suit;
- (id) initWithValue:(NSInteger) aValue suit:(Suit) aSuit;
#end
Card.m
#interface Card(Private)
- (NSString *) valueAsString;
- (NSString *) suitAsString;
#end
#implementation Card
#synthesize value,suit;
- (id) initWithValue:(NSInteger) aValue suit:(Suit) aSuit {
if(self = [super init]) {
self.value = aValue;
self.suit = aSuit;
}
return self;
}
- (NSString *) valueAsString {
switch (self.value) {
case Ace:
return #"Ace";
break;
case Jack:
return #"Jack";
break;
case Queen:
return #"Queen";
break;
case King:
return #"King";
break;
default:
return [NSString stringWithFormat:#"%d",self.value];
break;
}
}
- (NSString *) suitAsString {
switch (self.suit) {
case Hearts:
return #"Hearts";
break;
case Diamonds:
return #"Diamonds";
break;
case Spades:
return #"Spades";
break;
case Clubs:
return #"Clubs";
break;
default:
return nil;
break;
}
}
- (NSString *) description {
return [NSString stringWithFormat:#"%# %#",
[self valueAsString],
[self suitAsString]];
}
#end
Deck.h
#interface Deck : NSObject {
#private
NSMutableArray *cards;
}
- (void) shuffle;
- (Card *) draw;
- (NSInteger) cardsRemaining;
#end
Deck.m
#implementation Deck
- (id) init {
if(self = [super init]) {
cards = [[NSMutableArray alloc] init];
for(int suit = 0; suit <= 3; suit++) {
for(int value = 1; value <= 13; value++) {
Card *card = [[Card alloc] initWithValue:value suit:suit];
[cards addObject:card];
[card release];
}
}
}
return self;
}
int randomSort(id obj1, id obj2, void *context ) {
// returns random number -1 0 1
return (arc4random()%3 - 1);
}
- (void) shuffle {
for(int x = 0; x < 500; x++) {
[cards sortUsingFunction:randomSort context:nil];
}
}
- (Card *) draw {
if([self cardsRemaining] > 0) {
Card *card = [[cards lastObject] retain];
[cards removeLastObject];
return [card autorelease];
}
NSException* myException = [NSException
exceptionWithName:#"OutOfCardsException"
reason:#"Tried to draw a card from a deck with 0 cards." userInfo:nil]; #throw
myException;
}
- (NSInteger) cardsRemaining {
return [cards count];
}
- (NSString *) description {
NSString *desc = [NSString stringWithFormat:#"Deck with %d cards\n",[self
cardsRemaining]];
for(int x = 0; x < [self cardsRemaining]; x++) {
desc = [desc stringByAppendingFormat:#"%#\n",[[cards objectAtIndex:x] description]];
}
return desc;
}
- (void) dealloc {
[cards release];
[super dealloc];
}
#end
When you declare the property deckObject, that just creates the pointer, you still have to alloc init a Deck object to create an instance.
self.deckObject = [[Deck alloc] init];
Then, you can use [self.deckObject shuffle] and [self.deckObject deal]in different button methods.
you have to declaring Deck *DeckObject in viewController.h as below
#interface firstViewController : UIViewController {
Deck *DeckObject;
}
Now in viewController.m file's viewDidLoad method initialize it like below
Deck *DeckObject = [[Deck alloc] init];
after that you can call methods in uibutton's action method
- (IBAction)BtnShuffle:(id)sender
{
if (DeckObject) {
[DeckObject shuffle];
}
}
and
- (IBAction)BtnDraw:(id)sender
{
if (DeckObject) {
[DeckObject draw];
}
}
While implementing a subclass of NSArray (a class cluster), I was surprised to see that my overridden description method was not called. Can somebody explain what is happening here?
#interface MyArrayClassCluster : NSArray
#end
#implementation MyArrayClassCluster
{
NSArray *_realArray;
}
// Implement the class cluser stuff here
- (NSUInteger)count
{
return [_realArray count];
}
- (id)objectAtIndex:(NSUInteger)index
{
return [_realArray objectAtIndex:index];
}
// lifeCycle
- (id)initWithItems:(NSArray *)items
{
self = [super init];
_realArray = [items retain];
return self;
}
- (void)dealloc
{
[_realArray release];
[super dealloc];
}
- (NSString *)description
{
return [NSString stringWithFormat:#"My Custom Array: %p, objs:%#", self, _realArray];
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray *a = #[#1, #2, #3];
NSLog(#"a: %#", a);
MyArrayClassCluster *clzCluster = [[MyArrayClassCluster alloc] initWithItems:a];
NSLog(#"clzCluster: %#", clzCluster);
}
return 0;
}
Output
2013-01-29 18:52:38.704 ClassClusterTester[31649:303] a: (
1,
2,
3
)
2013-01-29 18:52:38.707 ClassClusterTester[31649:303] clzCluster: (
1,
2,
3
)
The link #Rob pointed to had the correct answer at the bottom. An obscure fact: if something implements descriptionWithLocale:, NSLog will call that instead. Since my class is a subclass of NSArray, and NSArray implements that, my version of description was not called.