I would like to read the list of colors that are shown on the bottom of the NSColorPanel (see image below). Is there a way to do this?
For undocumented access (this may not work within a sandbox and will get your app rejected by Apple if you plan to distribute through the App Store):
NSArray *libraries = [[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSAllDomainsMask];
NSURL *url = [[libraries objectAtIndex:0] URLByAppendingPathComponent:#"Colors/NSColorPanelSwatches.plist"];
NSData *fileData = [NSData dataWithContentsOfURL:url];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:fileData];
NSArray *colors = [unarchiver decodeObjectForKey:#"NSSwatchColorArray"];
The colors array will then contain NSColor objects of the colour panel.
This works as far back as OS X 10.6. It may work on earlier versions also but you'll need to obtain the filename differently (since URLsForDirectory:inDomains: was introduced in 10.6). Inside the NSColorPanelSwatches.plist file is an internal version number which is set to 6 for 10.6 right through to 10.10. It could change in the future, but you could be more-or-less safe by doing:
if ([unarchiver decodeIntForKey:#"NSSwatchFileVersion"] == 6)
{
NSArray *colors = [unarchiver objectForKey:#"NSSwatchColorArray"];
// do something with colors
}
else
{
NSLog(#"System unsupported");
}
If you're interested in where the positions of the colours are, you can decode an NSIndexSet from the unarchiver using the NSSwatchColorIndexes key, and use that index set in conjunction with the number of rows and columns that you can determine by decoding integers with the keys NSSwatchLayoutNumRows and NSSwatchLayoutNumColumns. The nth index in the index set refers to the location of the nth colour in the array and the indexes increase downwards. For example, the first “colour box” in the panel is index 0, and the box below it is index 1. The box to the right of “index 0” is actually index 10 (or whatever number you decoded from NSSwatchLayoutNumRows).
So if you have a colour in the first box, and another colour in the box to the right, you'll have two NSColor objects in the colors array, and the NSIndexSet will contain two indexes, 0 and 10.
Unfortunately there are no public APIs for accessing system-wide user-provided colors from the swatches. This is a very old issue. NSColorPanel is long overdue for an overhaul from Apple...
Related
Background Information:
I program an iOS application that contains 2 galleries: A local gallery and a server gallery. When the user updates the server gallery and merges it into his local one, the app should only download the new images.
To minimize memory consumption I save the images and fill the arrays with instances of an ImageEntity class with following attributes: fileName, filePath & votingStatus.
I tried to use following logic to check if the image already exists:
for (ImageEntity *imageEntity in self.serverImagesArray) {
if (![self.localImagesArray containsObject:imageEntity]){
[self.localImagesArray addObject:imageEntity];
}
}
But because each entity is a separate object it will always be added. Each entity has a unique fileName though.
Question:
Can I somehow extend the [NSArray containsObject:] function to check if one object in the array has an attribute equal to "someValue"? (When I use Cocoa-Bindings in combination with an ArrayController, I can assign attributes of array elements - I would like to access the attributes similar to this).
I know that I could use compare each entity of the local array to each element on the server array. I would have to do O(n^2) comparisons though and the gallery may contain several hundred images.
Bonus question: Am I already doing this without realizing it? Does anybody have details about Apple's implementation of this function? Is there some fancy implementation or are they just iterating over the array comparing every element?
The way I do it is use valueForKey: in combination with containsObject:. So in your case you should collect all file names of the array and then check if the array contains specific file name you need:
NSArray * fileNames = [fileEntityObjects valueForKey:#"fileName"];
BOOL contains = [fileNames containsObject:#"someFilename.jpg"];
This would work if fileName is property of every object in fileEntityObjects array.
Update
Yes, you can do this with NSPredicate as well:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"SELF.fileName = %#", "someFileName.jpg"];
NSArray * filteredArray = [fileEntityObjects filteredArrayUsingPredicate:predicate];
Note though that instead of boolean you'd get an array of objects having that filename.
As this question is tagged "performance" I'm adding another answer:
To avoid the n^2 comparisons we have to find a faster way to detect images that are already present. To do this we use a set that can perform lookups very quickly:
NSMutableSet *localFileNames = [NSMutableSet set];
for (ImageEntity *imageEntity in self.localImagesArray)
[localFileNames addObject:imageEntity.fileName];
Then we iterate over the server images as before. The previous containsObject: is replaced by the fast set lookup:
for (ImageEntity *imageEntity in self.serverImagesArray) {
if ([localFileNames member:imageEntity.fileName] == nil)
[self.localImagesArray addObject:imageEntity];
}
I need to show $value on a Label.
currently it appears as 125000 but i need it to be 125,000.00
thanks in advance to all supporters.
The solution of your problem is NSNumberFormatter
Some code to get you started:
NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSLog(#"%#", [currencyFormatter stringFromNumber:[NSNumber numberWithInt:10395209]]);
[currencyFormatter release];
Drag the Number Formatter (found in the object library) object onto the field/label. Change the behavior (be sure your in the attributes inspector for the number formatter) to 'OS X 10.4+ Custom' (that's what it was in Xcode 4.5.2).
In the 'Integer Digits' field, change the minimum to 1 and leave the maximum whatever you need. For the 'Fraction Digits' fields set the minimum and maximum to 2.
Near the top of the field, stick a dollar sign in front of the 'Format (+)' and '(-)' fields.
Check the group separator box then change the primary and secondary grouping fields to 3.
I have an array of UIImageViews:
NSMutableArray *imagesArray = [[NSMutableArray alloc] init];
[imagesArray addObject:smallBox];
[imagesArray addObject:medBox];
[imagesArray addObject:largeBox];
[imagesArray addObject:xlBox];
These boxes are being moved on the screen. I would like the array sorted so that whichever box is on the left side of the screen (x position) is in the first position of the array etc. I think I would be able to figure it out if I knew how to access the properties of the images while in the array. I tried using code like imagesArray[0].frame.origin.x; but that does not work. Any help would be appreciated.
I'd do it like this:
NSSortDescriptor *desc = [NSSortDescriptor sortDescriptorWithKey:#"frame.origin.x" ascending:YES];
[imagesArray sortUsingDescriptors:#[desc]];
That's assuming that all the objects in the array are views -- make sure that smallBox, medBox, etc. are all instances of UIImageView and not UIImage. Also, note that in order for the image views' x coordinate to be sort the way you want them to, they should all be subviews of the same superview. If they're not, you're not comparing apples to apples. If the views are not all subviews of the same view, you'll need to write a comparator that first converts the view's frame to screen coordinates before comparing.
Your expression (imagesArray[0].frame.origin.x) should give you the x coordinate of the view at imagesArray[0], and if it doesn't it'd be helpful to know how/why it's failing.
I just finished debugging the hell out of my project after I found it was working fine on the simulator but would crash when tested on a device with an EXC BAD ACCESS error at the #autoreleasepool using ARC.
I finally narrowed the problem down to where I had a custom init method for a custom class I created that accepted two structs as parameters. The structs were based off of the same definition, containing only 3 GLfloats to represent x, y and z data for use with positioning and rotation.
When I modified the custom init method to instead accept 6 GLfloats instead of two structs each containing 3 GLfloats, and then had the init method assign these GLfloats to the two appropriate instance variable structs of the class instead of assigning the previously passed structs directly to the instance variable structs, it all worked fine without error.
For a bit of clarification:
I had a struct defined like so:
struct xyz{
GLfloat x;
GLfloat y;
GLfloat z;
};
I then had two ivars of a custom class (lets call it foo) called position and rotation, both based off of this struct.
I then had a custom init method simplified to this:
-(id) initWithPosition: (struct xyz) setPosition rotation: (struct xyz) setRotation
{
self = [super init];
if(self) {
position = setPosition;
rotation = setRotation;
}
return self;
}
Which I called using something similar to:
struct xyz position;
struct xyz rotation;
// Fill position / rotation with data here....
foo *bar = [[foo alloc] initWithPosition: position rotation: rotation];
Which resulted in the EXC BAD ACCESS. So I changed the init method to this:
-(id) initWithPositionX: (GLfloat) xp Y: (GLfloat) yp Z: (GLfloat) zp RotationX: (GLfloat) xr Y: (GLfloat) yr Z: (GLfloat) zr
{
self = [super init];
if(self) {
position.x = xp;
position.y = yp;
position.z = zp;
rotation.x = xr;
rotation.y = yr;
rotation.z = zr;
}
return self;
}
And... all is well.
I mean, Im glad I "fixed" it, but Im not sure why I fixed it. Ive read that ARC has issues with using structs of objects, but my structs are only comprised of simple GLfloats, which aren't objects... right? Id rather know why this is working before I move on, and hopefully help some others out there experiencing the same problem.
Thanks,
- Adam Eisfeld
EDIT: Adding Source
Try as I might I cant seem to duplicate the issue in a test project, so there must be something wrong with my code as others have suggested and I haven't fixed the problem at all, only masked it until later, which is what I was afraid of.
So if anyone can take a look at this code to point out what might be going wrong here (if the issue is indeed in the init method, which maybe its not), Id greatly appreciate it. Its rather large so I understand if nobody is interested, but Ive commented it and Ill explain whats going on in further detail after the code snippet:
-(id) initWithName: (NSString*) setName fromFile: (NSString*) file
{
self = [super init];
if(self) {
//Initialize ivars
name = setName;
joints = [[NSMutableArray alloc] init];
animations = [[NSMutableArray alloc] init];
animationCount = 0;
frameCount = 0;
//Init file objects for reading
NSError *fileError;
NSStringEncoding *encoding;
//Load the specified file's contents into an NSString
NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:#"dat"] usedEncoding:encoding error:&fileError];
//Create a new NGLMaterial to apply to all of the loaded models from the file
NGLMaterial *material = [[NGLMaterial alloc] init];
material = [NGLMaterial material];
material.diffuseMap = [NGLTexture texture2DWithFile:#"resources/models/diffuse.bmp"];
//Check for nil file data
if(fileData == nil)
{
NSLog(#"Error reading mesh file");
}
else
{
//Separate the NSString of the file's contents into an array, one line per indice
NSMutableArray *fileLines = [[NSMutableArray alloc] initWithArray:[fileData componentsSeparatedByString:#"\n"] copyItems: YES];
//Create a pseudo counter variable
int i = -1;
//Allocate a nul NSString for looping
NSString *line = [[NSString alloc] initWithFormat:#""];
//Loop through each of the lines in the fileLines array, parsing the line's data
for (line in fileLines) {
//Increase the pseudo counter variable to determine what line we're currently on
i++;
if (i == 0) {
//The first line of the file refers to the number of animations for the player
animationCount = [line intValue];
}else
{
if (i == [fileLines count]-2) {
//The last line of the file refers to the frame count for the player
frameCount = [line intValue];
}else
{
//The lines inbetween the first and last contain the names of the .obj files to load
//Obtain the current .obj path by combining the name of the model with it's path
NSString *objPath = [[NSString alloc] initWithFormat:#"resources/models/%#.obj", line];
//Instantiate a new NGLMesh with the objPath NSString
NGLMesh *newMesh = [[NGLMesh alloc] initWithOBJFile:objPath];
//Apply various settings to the mesh such as material
newMesh.material = material;
newMesh.rotationOrder = NGLRotationOrderZYX;
//Compile the changes to the mesh
[newMesh compileCoreMesh];
//Add the mesh to this player's joints array
[joints addObject:newMesh];
//Read the animation data for this joint from it's associated file
NSLog(#"Reading animation data for: %#", line);
//The associated animation file for this model is found at (model's name).anim
NSString *animData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:line ofType:#"anim"] usedEncoding:encoding error:&fileError];
//Check for nil animation data
if(animData == nil)
{
NSLog(#"Error reading animation file");
}
else
{
//Construct temporary position and rotation structs to store the read xyz data from each frame of animation
struct xyz position;
struct xyz rotation;
//Create a new scanner to scan the current animation file for it's xyz position / rotation data per frame
NSScanner *scanner = [[NSScanner alloc] initWithString:animData];
while([scanner isAtEnd] == NO)
{
//Extract position data
[scanner scanFloat:&position.x];
[scanner scanFloat:&position.y];
[scanner scanFloat:&position.z];
//Extract rotation data
[scanner scanFloat:&rotation.x];
[scanner scanFloat:&rotation.y];
[scanner scanFloat:&rotation.z];
//OLD CODE NOT WORKING:
//AEFrame *frame = [[AEFrame alloc] initWithPosition: position rotation: rotation];
//Initialize new frame instance using new working init method
AEFrame *frame = [[AEFrame alloc] initWithPositionX:position.x Y:position.y Z:position.z RotationX:rotation.x Y:rotation.y Z:rotation.z];
//Add the created frame instace to the player's animations array
[animations addObject:frame];
}
}
}
}
}
}
}
return self;
}
Alright so essentially I am writing the code that handles animating the 3D joints of a given player in my engine. I wrote a script for Maya in MEL that allows you to select a series of animated models in the viewport (in this case the joints of a robot character IE the upper and lower arms, the head, the torso, etc) and then run the script which will go through each selected model and export a .anim file. This .anim file contains the xyz position and rotation of the joint the file refers to at each frame of it's animation, so that it is structured like this:
Frame 1 X Position
Frame 1 Y Position
Frame 1 Z Position
Frame 1 X Rotation
Frame 1 Y Rotation
Frame 1 Z Rotation
Frame 2 X Position
Frame 2 Y Position
etc...
However this animation file consists purely of the floats refering to the xyz positions and rotations, there is no actual text labeling each line as shown above, thats only for reference.
When the exporting of .anim files is done for each selected joint of the animated character, the script exports one final file with a .dat extension. This file contains the amount of animations exported (this value is set in the script, for example you might be exporting 3 animations to a .anim file such as Run, Walk and Idle), it then contains a list of names. These names refer to both the names of the .obj files to load into the 3D engine and also the corresponding .anim files to load for the loaded .obj files.
With that explained, Ill describe how Im handling this in my project:
The user instantiates a new "AEPlayer" class using the above method -initWithName: File:
This method first opens up the .dat file exported from maya to find the names of the files to load in for the player's joints.
For every joint name found in the .dat file, the system proceeds to then instantiate a new NGLMesh instance using the joint's name with a ".obj" at the end of it, and then open up the current joint's .anim file. The system also add's the instantiated NGLMesh to the player's joints array.
For each joint's .anim file, it reads through the file using an NSScanner. It scans in 6 lines at a time (the xyz position and xyz rotation of each frame). After scanning in one frame's data, it instantiates a class called AEFrame and sets the frame's position data to the loaded position data and rotation data to the loaded rotation data. This is the class that I was having trouble with passing the xyz position struct and xyz rotation struct to in it's init parameters but "fixed" by passing in 6 GLfloats instead, as shown in the above code.
When the frame has been instantiated, this frame is added to the current player's animations array (an NSMutableArray). This animations array ends up getting fairly large, as it stores all of the animation data for every joint in the model. The model I am testing with has 42 joints, each joint has 12 frames of animation, each frame has 6 GLfloats.
When the system is finished loading all of the .obj files found in the .dat file and their associated .obj and .anim files into memory, it is finished.
In the main run loop of the program, I simply loop through all of the created AEPlayers, and for each AEPLayer I loop through the player's joints array, setting the position and rotation of the NGLMesh stored at the current player's joint to whatever position / rotation is found in the player's animations array for the current joint.
I realize its a long shot to get anyone to read all of that, but I thought Id throw it out there. Also, Im aware that I have a few dead allocations in the above snippet that I need to get rid of, but they're not contributing to the problem (I added them after the problem arose to see what was happening memory wise using Instruments).
Thanks a lot for any help again,
- Adam Eisfeld
It's possible some other code is corrupting the stack, or doing something else that's undefined. If you're doing anything like that (which can be really tricky to debug), all bets are off, and simple changes might mask or unmask the real bug.
When you change between device and simulator, you're also building for ARM instead of i386 and are building optimized instead of debug. Those are all huge changes and could change the result of the undefined behavior. For example, writing one byte past the end of an array could result in no harm in a debug build, but catastrophic failure in a release build. Changing the structs to floats could change the way the parameters are passed into your method, which could either mask or unmask the bug.
You should try to isolate your problem to a small sample project. For example, can you reproduce the problem with just one class, with just that one init method?
This set of articles from the LLVM blog might be helpful if you're not already familiar with this topic:
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html
Update:
It looks like your problem may be here:
NSStringEncoding *encoding;
//Load the specified file's contents into an NSString
NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:#"dat"] usedEncoding:encoding error:&fileError];
The usedEncoding parameter of -[NSString initWithContentsOfFile:usedEncoding:error] is an out parameter, meaning that you provide storage and pass a pointer to that storage and the function will write some output to it. You've provided a pointer, but it doesn't point at anything valid, and when -[NSString initWithContentsOfFile:usedEncoding:error] writes to the usedEncoding pointer, it will overwrite something.
You should change that code to this:
NSStringEncoding encoding;
//Load the specified file's contents into an NSString
NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:#"dat"] usedEncoding:&encoding error:&fileError];
Now you've provided a pointer to a valid string encoding variable.
Is there any equivalent method in AppKit (for Cocoa on Mac OS X) that does the same thing as UIKit's [NSString sizeWithFont:constrainedToSize:]?
If not, how could I go about getting the amount of space needed to render a particular string constrained to a width/height?
Update: Below is a snippet of code I'm using that I expect would produce the results I'm after.
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize: [NSFont smallSystemFontSize]], NSFontAttributeName,
[NSParagraphStyle defaultParagraphStyle], NSParagraphStyleAttributeName,
nil];
NSSize size = NSMakeSize(200.0, MAXFLOAT);
NSRect bounds;
bounds = [#"This is a really really really really really really really long string that won't fit on one line"
boundingRectWithSize: size
options: NSStringDrawingUsesFontLeading
attributes: attributes];
NSLog(#"height: %02f, width: %02f", bounds.size.height, bounds.size.width);
I would expect that the output width would be 200 and the height would be something greater than the height of a single line, however it produces:
height: 14.000000, width: 466.619141
Thanks!
Try this one:
bounds = [value boundingRectWithSize:size options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin attributes:attributes];
The newer NSExtendedStringDrawing category API (methods with the NSStringDrawingOptions argument) behaves in the single line mode. If you want to measure/render in multi line mode, want to specify NSStringDrawingUsesLineFragmentOrigin.
EDIT: You should be able to do things the normal way in Lion and later. The problems described below have been fixed.
There is no way to accurately measure text among the current Mac OS X APIs.
There are several APIs that promise to work but don't. That's one of them; Core Text's function for this purpose is another. Some methods return results that are close but wrong; some return results that seem to mean nothing at all. I haven't filed any bugs on these yet, but when I do, I'll edit the Radar numbers into this answer. You should file a bug as well, and include your code in a small sample project.
[Apparently I have already filed the Core Text one: 8666756. It's closed as a duplicate of an unspecified other bug. For Cocoa, I filed 9022238 about the method Dave suggested, and will file two NSLayoutManager bugs tomorrow.]
This is the closest solution I've found.
If you want to constrain the string to a certain size, you use -[NSString boundingRectWithSize:options:attributes:]. The .size of the returned NSRect is the size you're looking for.
Here is a more complete example of how you can do this using boundingRectWithSize.
// get the text field
NSTextField* field = (NSTextField*)view;
// create the new font for the text field
NSFont* newFont = [NSFont fontWithName:#"Trebuchet MS" size:11.0];
// set the font on the text field
[field setFont:newFont];
// calculate the size the textfield needs to be in order to display the text properly
NSString* text = field.stringValue;
NSInteger maxWidth = field.frame.size.width;
NSInteger maxHeight = 20000;
CGSize constraint = CGSizeMake(maxWidth, maxHeight);
NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys:NSFontAttributeName,newFont, nil];
NSRect newBounds = [text boundingRectWithSize:constraint
options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin
attributes:attrs];
// set the size of the text field to the calculated size
field.frame = NSMakeRect(field.frame.origin.x, field.frame.origin.y, field.frame.size.width, newBounds.size.height);
Of course, for additional info, take a look at the Apple documentation:
Options for the attributes dictionary
boundingRectWithSize
If you search the documentation for NSString, you will find the "NSString Application Kit Additions Reference", which is pretty much analogous to the UIKit counterpart.
-[NSString sizeWithAttributes:]
is the method you are looking for.