Store a NSColor as a string - objective-c

I currently have a Core Data database that stores data and I wish to also store an NSColor into it but It does not accept NSColor as an object. My solution would be to store it as a string in the database and have it read into a NSColor when loaded. How would I do this?
For example, If I had a colour like [NSColor redColor] how would I store it in a database (as a string) and then retrieve it. This is a basic example and it would be more complicated RGB colors in the end.
Thanks.

You should consider using NSData as container for storing unsupported data types in Core Data. To access NSColor as NSData you will need to mark attribute as transformable and create reversible NSValueTransformer class to transform NSColor as NSData.
Useful Link: Non-Standard Persistent Attributes

I agree with the answers that recommend using NSData for storing colors in a Core Data store. That said, there may be times when it might be useful to store a color in a string, and it's certainly not difficult to do. I'd suggest creating a category on NSColor:
#interface NSColor (NSString)
- (NSString*)stringRepresentation;
+ (NSColor*)colorFromString:(NSString*)string forColorSpace:(NSColorSpace*)colorSpace;
#end
#implementation NSColor (NSString)
- (NSString*)stringRepresentation
{
CGFloat components[10];
[self getComponents:components];
NSMutableString *string = [NSMutableString string];
for (int i = 0; i < [self numberOfComponents]; i++) {
[string appendFormat:#"%f ", components[i]];
}
[string deleteCharactersInRange:NSMakeRange([string length]-1, 1)]; // trim the trailing space
return string;
}
+ (NSColor*)colorFromString:(NSString*)string forColorSpace:(NSColorSpace*)colorSpace
{
CGFloat components[10]; // doubt any color spaces need more than 10 components
NSArray *componentStrings = [string componentsSeparatedByString:#" "];
int count = [componentStrings count];
NSColor *color = nil;
if (count <= 10) {
for (int i = 0; i < count; i++) {
components[i] = [[componentStrings objectAtIndex:i] floatValue];
}
color = [NSColor colorWithColorSpace:colorSpace components:components count:count];
}
return color;
}
#end
I've checked that the code above compiles and works about as advertised. A small sample program produces appropriate output:
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSLog(#"Red is: %#", [[NSColor redColor] stringRepresentation]);
NSLog(#"Cyan is: %#", [[NSColor cyanColor] stringRepresentation]);
NSLog(#"Read in: %#", [NSColor colorFromString:[[NSColor redColor] stringRepresentation]
forColorSpace:[NSColorSpace deviceRGBColorSpace]]);
}
return 0;
}
Output:
Red is: 1.000000 0.000000 0.000000 1.000000
Cyan is: 0.000000 1.000000 1.000000 1.000000
Read in: NSCustomColorSpace Generic RGB colorspace 1 0 0 1
It might make sense to store the color space in the string so you don't have to specify it when you go from string to color. Then again, if you're just going to store these strings and read them again, you should be using NSData anyway. Using strings makes more sense if you need to write colors into some sort of human-readable file, or perhaps as a debugging aid.

NSColor supports the NSCoding protocol, so you can use the -encodeWithCoder: method to save it to an archive, and you can use -initWithCoder: to load it from an archive.

Property lists do not store colors and Apple recommends you store them as NSData not as NSString, you should probably do the same. See Apple's instructions here.

Here are simple functions for converting an NSColor to and from an NSString. This example assumes we're using an RGB color space, but
it can be easily adapted for others. For example, NSStringFromColor() could include the color space in the string and use that information when converting back to a color in NSColorFromString().
Usage:
NSString *theColorString = NSStringFromColor(theColor);
NSColor *theColor = NSColorFromString(theColorString);
The functions:
NSString *NSStringFromColor(NSColor *theColor)
{
CGFloat red, green, blue, alpha;
[theColor getRed:&red green:&green blue:&blue alpha:&alpha]; // assumes RGB color space
NSString *theColorString = [NSString stringWithFormat:#"%f %f %f %f",red,green,blue,alpha];
return theColorString;
}
NSColor *NSColorFromString(NSString *theColorString)
{
if ( theColorString.length == 0 ) {
theColorString = #"0.9 0.9 0.95 1.0"; // a default color
}
NSArray <NSString *> *theColors = [theColorString componentsSeparatedByString:#" "];
if ( theColors.count == 4 ) { // sanity
// unpack the color
NSColor *theColor = [NSColor colorWithSRGBRed:theColors[0].floatValue
green:theColors[1].floatValue
blue:theColors[2].floatValue
alpha:theColors[3].floatValue];
return theColor;
}
return nil; // theColorString format error
}

Related

Padding Spaces in a NSString

The following question is for Objective C preferably (Swift is fine too). How can I get my strings to look like the strings in the picture below? The denominators and the right bracket of the percentage portions need to line up. Obviously the percentages could be 100%, 0%, 0%, which means that the left bracket for the percentages wouldn't line up, which is fine. The amount of space that the percentage part requires would be 9 spots.
I would strongly encourage using the layout engine for such things, but you could simulate yourself with something like the following, which I haven't tested...
// given a prefix, like #"5/50" and a suffix like #"(80%)", return a string where they are combined
// add leading spaces so that the prefix is right-justified to a particular pixel position
//
- (NSString *)paddedPrefix:(NSString *)prefix andSuffix:(NSString *)suffix forLabel:(UILabel *)label {
// or get maxWidth some other way, depends on your app
CGFloat maxWidth = [self widthOfString:#"88888/50" presentedIn:label];
NSMutableString *mutablePrefix = [prefix mutableCopy];
CGFloat width = [self widthOfString:mutablePrefix presentedIn:label];
while (width<maxWidth) {
[mutablePrefix insertString:#" " atIndex:0];
}
// the number of blanks between the prefix and suffix is also up to you here:
return [NSString stringWithFormat:#"%# %#", mutablePrefix, suffix];
}
// answer the width of the passed string assuming an infinitely wide label (no wrapping)
//
- (CGFloat)widthOfString:(NSString *)string presentedIn:(UILabel *)label {
NSAttributedString *as = [[NSAttributedString alloc] initWithString:string attributes:#{NSFontAttributeName:label.font}];
CGRect rect = [as boundingRectWithSize:(CGSize){CGFLOAT_MAX, CGFLOAT_MAX}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
return rect.size.width;
}

string not populating with array data, even though arrays are not really empty

I am trying to create a non-Document-based application for Mac OS X that randomizes cards for the game of Dominion.
From many of the ones I have tried, the only thing I cannot seem to do is limit the number of sets picked from a selection made by the user, and things worked pretty well in my program, but I am having issues.
I am trying to get the results to print in a custom view, but every time I look at the print preview, nothing shows, except header text, as specified in an NSMutableString.
This piece of code is what is being used to print and is found in MasterViewController:
- (IBAction)print:(id)sender
{
NSMutableString *content = [[NSMutableString alloc] initWithString:#"Cards\r\n\r\n"];
for (int i = 0; i < [supply.game count]; i++)
{
[content appendFormat:#"Card: %# Set: %# Cost: %d\r\n", [supply.game[i] name], [supply.game[i] collection], [supply.game[i] cost]];
}
[content appendFormat:#"\r\n\r\nRequired\r\n\r\n"];
for (int i = 0; i < [[setup supply] count]; i++)
{
NSDictionary* current = [setup supply][i];
NSString* key = [current allKeys][0]; // get the key of the current dictionary must be 0, as there is only one key
int value = [[current valueForKey:key] integerValue]; // variable to hold key value
if (value > 0) {
[content appendFormat:#"%#: %#", key, #"Yes"];
}
else
{
[content appendFormat:#"%#: %#", key, #"No"];
}
}
printView.content = [NSMutableString stringWithString:content];
[printView print:sender];
}
the data initially gets filled into some tableviews, which displays the correct content, and the supply.game array is the exact array that contains cards used for games.
setup is a property that refers to a view controller that populates a table with kinds of cards that may be required for games (e.g. shelters, colonies, ruins, spoils, and potions) and the supply method is supposed to return the array that view controller creates, which is itself not empty, as that table populates properly.
printView is a property that is assigned to a custom view found in MainMenu.xib and is the real view being used to print from.
the printView class looks like this:
header:
#import <Cocoa/Cocoa.h>
#interface PrintView : NSView
{
NSMutableString* content;
}
#property NSMutableString* content;
- (void)drawStringInRect:(NSRect)rect; // method to draw string to page
- (void)print:(id)sender; // method to print
#end
implementation:
#import "PrintView.h"
#implementation PrintView
#synthesize content;
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (void)print:(id)sender
{
[[NSPrintOperation printOperationWithView:self] runOperation];
}
- (void)drawRect:(NSRect)dirtyRect {
NSGraphicsContext *context = [NSGraphicsContext currentContext];
if ([context isDrawingToScreen])
{
}
else
{
[[NSColor whiteColor] set];
NSRect bounds = [self bounds];
if (content == nil || [content length] == 0)
{
NSRectFill(bounds);
}
else
{
[self drawStringInRect:bounds];
}
}
}
- (void)drawStringInRect:(NSRect)rect
{
NSSize strSize; // variable to hold string size
NSPoint strOrigin; // variable used to position text
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
[attributes setObject:[NSFont fontWithName:#"Helvetica" size:12] forKey:NSFontAttributeName];
[attributes setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];
strSize = [content sizeWithAttributes:attributes];
strOrigin.x = rect.origin.x + (rect.size.width - strSize.width)/2;
strOrigin.y = rect.origin.y + (rect.size.height - strSize.height)/2;
[content drawAtPoint:strOrigin withAttributes:attributes];
}
#end
When I check the array sizes for printing operation, the size of the arrays is reported as zero, thus resulting in my current problem
If you need more code, here is code from Github, but I do not have the experimental branch up there, which is where the above code came from, though it should not be too different.
The MasterViewController will show how the supply.game array is made and SetupViewController houses the code that is used to determine what is needed in the game, as well as show how the supply array from [setup supply] is being produced.
MasterViewController has also been added as an object to MainMenu.xib, so I do not know if that affects anything.
Any idea of what I need to do?
Edit: Added in info that might be relevant

NSDecimalNumber addition losing decimal places

I am trying to make a calculator iphone app and my teacher said we had to use NSDecimalNumber. I am having a lot of trouble with it. I am trying to get the addition part of it right, but I am having trouble when adding a number like 1.24 to a whole number like 33. The result comes out to be 34 when I want it to be 34.24. Does anyone know how to make it so it comes out this way? Here is the relevant parts of the code
-(void)outputNumber: (NSDecimalNumber*) number
{
//used to format number of decimal places
NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease];
[formatter setMaximumFractionDigits:self.afterDecimal];
//output the number to calculator
NSString* formatNumber = [formatter stringFromNumber:[NSNumber numberWithDouble:[number doubleValue]]];
self.inputLabel.text = [NSString stringWithFormat: #"%#", formatNumber];
if (self.isWaiting == TRUE) {
self.numberB = number;
//change button title default color
[self.myButton setTitleColor:[UIColor colorWithRed:.196 green: .3098 blue: .52 alpha: 1] forState: 0];
}
else {
self.numberA = number;
}
}
if (self.binaryTag == 6) {
self.numberC = [self.numberB decimalNumberByAdding: self.numberA];
}
Actually NSNumberFormatter doesn't work correctly with NSDecimalNumber. It converts everything to a double first.
When using NSDecimalNumber, try to use its own methods to round it and then convert it into NSString. With a NSNumberFormatter you will just lose the precision.

RTL & LTR (bidirectional) in UITextView

I'm trying to save the content of a UITextView which contains lines of text formatted both RTL and LTR.
The problem is that UITextView checks only the first character to format direction. Let's assume I'm in "edit" mode and write this text (__ means spaces):
text1_______________________________________
____________________________________________אקסא
text2_______________________________________
and after saving we lost RTL for אקסא. Now I'd like to edit this text once again which now looks like:
text1_______________________________________
אקסא
text2_______________________________________
I'm not able to mix \u200F with \u200E directional characters in one UITextView.
How to manage this and save correctly bidirectional text from UITextView?
Here is a quick proof of concept using NSAttributedString :
- Split the text in paragraphs
- For each paragraph, detect the main language
- Create an attributed text with the correct alignmenent for the corresponding range
// In a subclass of `UITextView`
+ (UITextAlignment)alignmentForString:(NSString *)astring {
NSArray *rightToLeftLanguages = #[#"ar",#"fa",#"he",#"ur",#"ps",#"sd",#"arc",#"bcc",#"bqi",#"ckb",#"dv",#"glk",#"ku",#"pnb",#"mzn"];
NSString *lang = CFBridgingRelease(CFStringTokenizerCopyBestStringLanguage((CFStringRef)astring,CFRangeMake(0,[astring length])));
if (astring.length) {
if ([rightToLeftLanguages containsObject:lang]) {
return NSTextAlignmentRight;
}
}
return NSTextAlignmentLeft;
}
- (void)setText:(NSString *)str { // Override
[super setText:str];
// Split in paragraph
NSArray *paragraphs = [self.text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
// Attributed string for the whole string
NSMutableAttributedString *attribString = [[NSMutableAttributedString alloc]initWithString:self.text];
NSUInteger loc = 0;
for(NSString *paragraph in paragraphs) {
// Find the correct alignment for this paragraph
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
[paragraphStyle setAlignment:[WGTextView alignmentForString:paragraph]];
// Find its corresponding range in the string
NSRange range = NSMakeRange(loc, [paragraph length]);
// Add it to the attributed string
[attribString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
loc += [paragraph length];
}
[super setAttributedText:attribString];
}
Also, I recommend reading the Unicode BiDi Algorithm to manage more complex use cases.

How to judge the color of UILabel?

UILabel *templabel = [self.wallBoxArray objectAtIndex:i];
for( int i = 0 ; i < [self.wallBoxArray count]; i++)
{
if(templabel.backgroundColor == [UIColor greenColor])
{
NSLog(#"the color isn green");
}
}
There are many label's in my array. They all initialized with green color. But i judged that way ,why cant print " the color isn't green.
The UIColor class cluster implements -isEqual:, so you could just use
if([templabel.backgroundColor isEqual:[UIColor greenColor]])
...
You are performing a pointer comparision there, so if the color's are both green, but different instances of UIColor, this will fail. And they are because UIView's backgroundColor property is a copy property.
#property(nonatomic, copy) UIColor *backgroundColor
I'm sort of surprised this is that convoluted, but to check for equality, try the following:
CGColorEqualToColor([templabel.backgroundColor CGColor], [[UIColor greenColor] CGColor])
This is checking equality of the color value, not just a pointer comparison. Also remember to do [str compare:otherString] == NSOrderSame when checking strings!