I'm trying to remove a dependency from MKMapKit for one of our framework. We use MKMapKit only to calculate the distance between two coordinates, using the function MKMetersBetweenMapPoints.
Implementing the same code but using CLLocation object and the distanceFromLocation method I receive different results. The error is less than one meter.
This is the test I'm running
- (double)distanceWithMapKitFrom:(CLLocationCoordinate2D)fromLocationCoordinate2D toCoordinate:(CLLocationCoordinate2D)toLocationCoordinate2D
{
MKMapPoint mapPointFrom = MKMapPointForCoordinate(fromLocationCoordinate2D);
MKMapPoint mapPointTo = MKMapPointForCoordinate(toLocationCoordinate2D);
return MKMetersBetweenMapPoints(mapPointFrom, mapPointTo) / 1000;
}
- (double)distanceWithCoreLocationFrom:(CLLocationCoordinate2D)fromLocationCoordinate2D toCoordinate:(CLLocationCoordinate2D)toLocationCoordinate2D
{
CLLocation *fromLocation = [[CLLocation alloc] initWithLatitude:fromLocationCoordinate2D.latitude longitude:fromLocationCoordinate2D.longitude];
CLLocation *toLocation = [[CLLocation alloc] initWithLatitude:toLocationCoordinate2D.latitude longitude:toLocationCoordinate2D.longitude];
return [fromLocation distanceFromLocation:toLocation] / 1000;
}
- (void)testDistanceWithoutMapKit
{
for (int i = 0; i < 100; i++) {
CLLocationCoordinate2D coord1 = RandomCoordinate2D();
CLLocationCoordinate2D coord2 = RandomCoordinate2D();
double distance1 = [self distanceWithMapKitFrom:coord1 toCoordinate:coord2];
double distance2 = [self distanceWithCoreLocationFrom:coord1 toCoordinate:coord2];
NSLog(#"%f %f %f", distance1, distance2, distance2 - distance1);
XCTAssertEqual(distance1, distance2);
}
}
The RandomCoordinate2D function can be found here
Related
I'm using the GPUImage framework to take the average red values of some camera input. I'm storing all of the average red values in an array, and I'm trying this:
NSMutableArray *redValues = [NSMutableArray array];
__block int counter = 0;
GPUImageVideoCamera *videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack];
videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
GPUImageAverageColor *averageColor = [[GPUImageAverageColor alloc] init];
[averageColor setColorAverageProcessingFinishedBlock:^(CGFloat redComponent, CGFloat greenComponent, CGFloat blueComponent, CGFloat alphaComponent, CMTime frameTime)
{
NSLog(#"%f", redComponent);
[redValues addObject:#(redComponent)];
counter++;
}];
for (int i = 0; i < counter; i++)
{
redValues[i] *= 255; // errors occurs here
}
The error you get is because you are trying to multiply an NSNumber by an int. However, there is a deeper logical issue in that you are receiving redComponent as a value between 0 and 1. When you convert to an int, you truncate almost all values to 0 (at which point multiplying by 255 will not help much). Instead, you should inline the multiplication (and eliminate the second loop):
averageColor.colorAverageProcessingFinishedBlock = ^(CGFloat redComponent, CGFloat greenComponent, CGFloat blueComponent, CGFloat alphaComponent, CMTime frameTime) {
NSLog(#"%f", redComponent);
[redValues addObject:#(redComponent * 255)];
};
I am using a NSRulerView in MacOS in order to display line numbers next to a NSTextView.
Both views share the same font and the same font size, however while in NSTextView string rendering is automatically managed, in the NSRulerView I need to compute correct line number (and this part works fine) and then render the string inside a drawHashMarksAndLabelsInRect.
My issue is that I am unable to correctly align text between the two views. For some font it works fine while for other fonts there are visible differences.
The code I am actually using is:
#define BTF_RULER_WIDTH 40.0f
#define BTF_RULER_PADDING 5.0f
static inline void drawLineNumber(NSUInteger lineNumber, CGFloat y, NSDictionary *attributes, CGFloat ruleThickness) {
NSString *string = [[NSNumber numberWithUnsignedInteger:lineNumber] stringValue];
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
NSUInteger x = ruleThickness - BTF_RULER_PADDING - attString.size.width;
[attString drawAtPoint:NSMakePoint(x, y)];
}
static inline NSUInteger countNewLines(NSString *s, NSUInteger location, NSUInteger length) {
CFStringInlineBuffer inlineBuffer;
CFStringInitInlineBuffer((__bridge CFStringRef)s, &inlineBuffer, CFRangeMake(location, length));
NSUInteger counter = 0;
for (CFIndex i=0; i < length; ++i) {
UniChar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i);
if (c == (UniChar)'\n') ++counter;
}
return counter;
}
#implementation BTFRulerView
- (instancetype)initWithBTFTextView:(BTFTextView *)textView {
self = [super initWithScrollView:textView.enclosingScrollView orientation:NSVerticalRuler];
if (self) {
self.clientView = textView;
// default settings
self.ruleThickness = BTF_RULER_WIDTH;
self.textColor = [NSColor grayColor];
}
return self;
}
- (void)drawHashMarksAndLabelsInRect:(NSRect)rect {
// do not use drawBackgroundInRect for background color otherwise a 1px right border with a different color appears
if (_backgroundColor) {
[_backgroundColor set];
[NSBezierPath fillRect:rect];
}
BTFTextView *textView = (BTFTextView *)self.clientView;
if (!textView) return;
NSLayoutManager *layoutManager = textView.layoutManager;
if (!layoutManager) return;
NSString *textString = textView.string;
if ((!textString) || (textString.length == 0)) return;
CGFloat insetHeight = textView.textContainerInset.height;
CGPoint relativePoint = [self convertPoint:NSZeroPoint fromView:textView];
NSDictionary *lineNumberAttributes = #{NSFontAttributeName: textView.font, NSForegroundColorAttributeName: _textColor};
NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:textView.visibleRect inTextContainer:textView.textContainer];
NSUInteger firstVisibleGlyphCharacterIndex = [layoutManager characterIndexForGlyphAtIndex:visibleGlyphRange.location];
// line number for the first visible line
NSUInteger lineNumber = countNewLines(textString, 0, firstVisibleGlyphCharacterIndex)+1;
NSUInteger glyphIndexForStringLine = visibleGlyphRange.location;
// go through each line in the string
while (glyphIndexForStringLine < NSMaxRange(visibleGlyphRange)) {
// range of current line in the string
NSRange characterRangeForStringLine = [textString lineRangeForRange:NSMakeRange([layoutManager characterIndexForGlyphAtIndex:glyphIndexForStringLine], 0)];
NSRange glyphRangeForStringLine = [layoutManager glyphRangeForCharacterRange: characterRangeForStringLine actualCharacterRange:nil];
NSUInteger glyphIndexForGlyphLine = glyphIndexForStringLine;
NSUInteger glyphLineCount = 0;
while (glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine)) {
// check if the current line in the string spread across several lines of glyphs
NSRange effectiveRange = NSMakeRange(0, 0);
// range of current "line of glyphs". If a line is wrapped then it will have more than one "line of glyphs"
NSRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndexForGlyphLine effectiveRange:&effectiveRange withoutAdditionalLayout:YES];
// compute Y for line number
CGFloat y = NSMinY(lineRect) + relativePoint.y + insetHeight;
// draw line number only if string does not spread across several lines
if (glyphLineCount == 0) {
drawLineNumber(lineNumber, y, lineNumberAttributes, self.ruleThickness);
}
// move to next glyph line
++glyphLineCount;
glyphIndexForGlyphLine = NSMaxRange(effectiveRange);
}
glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine);
++lineNumber;
}
// draw line number for the extra line at the end of the text
if (layoutManager.extraLineFragmentTextContainer) {
CGFloat y = NSMinY(layoutManager.extraLineFragmentRect) + relativePoint.y + insetHeight;
drawLineNumber(lineNumber, y, lineNumberAttributes, self.ruleThickness);
}
}
I think that the issue is the y computation then passed to the drawLineNumber function. Any idea about how to correctly compute it?
I found a solution and I think it could be quite useful to others:
#define BTF_RULER_WIDTH 40.0f
#define BTF_RULER_PADDING 5.0f
static inline void drawLineNumberInRect(NSUInteger lineNumber, NSRect lineRect, NSDictionary *attributes, CGFloat ruleThickness) {
NSString *string = [[NSNumber numberWithUnsignedInteger:lineNumber] stringValue];
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
NSUInteger x = ruleThickness - BTF_RULER_PADDING - attString.size.width;
// Offetting the drawing keeping into account the ascender (because we draw it without NSStringDrawingUsesLineFragmentOrigin)
NSFont *font = attributes[NSFontAttributeName];
lineRect.origin.x = x;
lineRect.origin.y += font.ascender;
[attString drawWithRect:lineRect options:0 context:nil];
}
static inline NSUInteger countNewLines(NSString *s, NSUInteger location, NSUInteger length) {
CFStringInlineBuffer inlineBuffer;
CFStringInitInlineBuffer((__bridge CFStringRef)s, &inlineBuffer, CFRangeMake(location, length));
NSUInteger counter = 0;
for (CFIndex i=0; i < length; ++i) {
UniChar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i);
if (c == (UniChar)'\n') ++counter;
}
return counter;
}
#implementation BTFRulerView
- (instancetype)initWithBTFTextView:(BTFTextView *)textView {
self = [super initWithScrollView:textView.enclosingScrollView orientation:NSVerticalRuler];
if (self) {
self.clientView = textView;
// default settings
self.ruleThickness = BTF_RULER_WIDTH;
self.textColor = [NSColor grayColor];
}
return self;
}
- (void)drawHashMarksAndLabelsInRect:(NSRect)rect {
// do not use drawBackgroundInRect for background color otherwise a 1px right border with a different color appears
if (_backgroundColor) {
[_backgroundColor set];
[NSBezierPath fillRect:rect];
}
BTFTextView *textView = (BTFTextView *)self.clientView;
if (!textView) return;
NSLayoutManager *layoutManager = textView.layoutManager;
if (!layoutManager) return;
NSString *textString = textView.string;
if ((!textString) || (textString.length == 0)) return;
CGFloat insetHeight = textView.textContainerInset.height;
CGPoint relativePoint = [self convertPoint:NSZeroPoint fromView:textView];
// Gettign text attributes from the textview
NSMutableDictionary *lineNumberAttributes = [[textView.textStorage attributesAtIndex:0 effectiveRange:NULL] mutableCopy];
lineNumberAttributes[NSForegroundColorAttributeName] = self.textColor;
NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:textView.visibleRect inTextContainer:textView.textContainer];
NSUInteger firstVisibleGlyphCharacterIndex = [layoutManager characterIndexForGlyphAtIndex:visibleGlyphRange.location];
// line number for the first visible line
NSUInteger lineNumber = countNewLines(textString, 0, firstVisibleGlyphCharacterIndex)+1;
NSUInteger glyphIndexForStringLine = visibleGlyphRange.location;
// go through each line in the string
while (glyphIndexForStringLine < NSMaxRange(visibleGlyphRange)) {
// range of current line in the string
NSRange characterRangeForStringLine = [textString lineRangeForRange:NSMakeRange([layoutManager characterIndexForGlyphAtIndex:glyphIndexForStringLine], 0)];
NSRange glyphRangeForStringLine = [layoutManager glyphRangeForCharacterRange: characterRangeForStringLine actualCharacterRange:nil];
NSUInteger glyphIndexForGlyphLine = glyphIndexForStringLine;
NSUInteger glyphLineCount = 0;
while (glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine)) {
// check if the current line in the string spread across several lines of glyphs
NSRange effectiveRange = NSMakeRange(0, 0);
// range of current "line of glyphs". If a line is wrapped then it will have more than one "line of glyphs"
NSRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndexForGlyphLine effectiveRange:&effectiveRange withoutAdditionalLayout:YES];
// compute Y for line number
CGFloat y = ceil(NSMinY(lineRect) + relativePoint.y + insetHeight);
lineRect.origin.y = y;
// draw line number only if string does not spread across several lines
if (glyphLineCount == 0) {
drawLineNumberInRect(lineNumber, lineRect, lineNumberAttributes, self.ruleThickness);
}
// move to next glyph line
++glyphLineCount;
glyphIndexForGlyphLine = NSMaxRange(effectiveRange);
}
glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine);
++lineNumber;
}
// draw line number for the extra line at the end of the text
if (layoutManager.extraLineFragmentTextContainer) {
NSRect lineRect = layoutManager.extraLineFragmentRect;
CGFloat y = ceil(NSMinY(lineRect) + relativePoint.y + insetHeight);
lineRect.origin.y = y;
drawLineNumberInRect(lineNumber, lineRect, lineNumberAttributes, self.ruleThickness);
}
}
I use drawWithRect instead of drawAtPoint and I use the attributes directly from the connected textView.
I'm trying to make an area calculation category for MKPolygon.
I found some JS code https://github.com/mapbox/geojson-area/blob/master/index.js#L1 with a link to the algorithm: http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409.
It says:
Here is my code, which gave a wrong result (thousands times more than actual):
#define kEarthRadius 6378137
#implementation MKPolygon (AreaCalculation)
- (double) area {
double area = 0;
NSArray *coords = [self coordinates];
if (coords.count > 2) {
CLLocationCoordinate2D p1, p2;
for (int i = 0; i < coords.count - 1; i++) {
p1 = [coords[i] MKCoordinateValue];
p2 = [coords[i + 1] MKCoordinateValue];
area += degreesToRadians(p2.longitude - p1.longitude) * (2 + sinf(degreesToRadians(p1.latitude)) + sinf(degreesToRadians(p2.latitude)));
}
area = area * kEarthRadius * kEarthRadius / 2;
}
return area;
}
- (NSArray *)coordinates {
NSMutableArray *points = [NSMutableArray arrayWithCapacity:self.pointCount];
for (int i = 0; i < self.pointCount; i++) {
MKMapPoint *point = &self.points[i];
[points addObject:[NSValue valueWithMKCoordinate:MKCoordinateForMapPoint(* point)]];
}
return points.copy;
}
double degreesToRadians(double radius) {
return radius * M_PI / 180;
}
#end
What did I miss?
The whole algorithm implemented in Swift 3.0 :
import MapKit
let kEarthRadius = 6378137.0
// CLLocationCoordinate2D uses degrees but we need radians
func radians(degrees: Double) -> Double {
return degrees * M_PI / 180;
}
func regionArea(locations: [CLLocationCoordinate2D]) -> Double {
guard locations.count > 2 else { return 0 }
var area = 0.0
for i in 0..<locations.count {
let p1 = locations[i > 0 ? i - 1 : locations.count - 1]
let p2 = locations[i]
area += radians(degrees: p2.longitude - p1.longitude) * (2 + sin(radians(degrees: p1.latitude)) + sin(radians(degrees: p2.latitude)) )
}
area = -(area * kEarthRadius * kEarthRadius / 2);
return max(area, -area) // In order not to worry about is polygon clockwise or counterclockwise defined.
}
The final step for i = N-1 and i+1 = 0 (wrap around) is missing in your loop.
This may help to someone...
You need to pass the shape edge points into below method and it returns the correct area of a polygon
static double areaOfCurveWithPoints(const NSArray *shapeEdgePoints) {
CGPoint initialPoint = [shapeEdgePoints.firstObject CGPointValue];
CGMutablePathRef cgPath = CGPathCreateMutable();
CGPathMoveToPoint(cgPath, &CGAffineTransformIdentity, initialPoint.x, initialPoint.y);
for (int i = 1;i<shapeEdgePoints.count ;i++) {
CGPoint point = [[shapeEdgePoints objectAtIndex:i] CGPointValue];
CGPathAddLineToPoint(cgPath, &CGAffineTransformIdentity, point.x, point.y);
}
CGPathCloseSubpath(cgPath);
CGRect frame = integralFrameForPath(cgPath);
size_t bytesPerRow = bytesPerRowForWidth(frame.size.width);
CGContextRef gc = createBitmapContextWithFrame(frame, bytesPerRow);
CGContextSetFillColorWithColor(gc, [UIColor whiteColor].CGColor);
CGContextAddPath(gc, cgPath);
CGContextFillPath(gc);
double area = areaFilledInBitmapContext(gc);
CGPathRelease(cgPath);
CGContextRelease(gc);
return area;
}
static CGRect integralFrameForPath(CGPathRef path) {
CGRect frame = CGPathGetBoundingBox(path);
return CGRectIntegral(frame);
}
static size_t bytesPerRowForWidth(CGFloat width) {
static const size_t kFactor = 64;
// Round up to a multiple of kFactor, which must be a power of 2.
return ((size_t)width + (kFactor - 1)) & ~(kFactor - 1);
}
static CGContextRef createBitmapContextWithFrame(CGRect frame, size_t bytesPerRow) {
CGColorSpaceRef grayscale = CGColorSpaceCreateDeviceGray();
CGContextRef gc = CGBitmapContextCreate(NULL, frame.size.width, frame.size.height, 8, bytesPerRow, grayscale, kCGImageAlphaNone);
CGColorSpaceRelease(grayscale);
CGContextTranslateCTM(gc, -frame.origin.x, -frame.origin.x);
return gc;
}
static double areaFilledInBitmapContext(CGContextRef gc) {
size_t width = CGBitmapContextGetWidth(gc);
size_t height = CGBitmapContextGetHeight(gc);
size_t stride = CGBitmapContextGetBytesPerRow(gc);
// Get a pointer to the data
unsigned char *bitmapData = (unsigned char *)CGBitmapContextGetData(gc);
uint64_t coverage = 0;
for (size_t y = 0; y < height; ++y) {
for (size_t x = 0; x < width; ++x) {
coverage += bitmapData[y * stride + x];
}
}
// NSLog(#"coverage =%llu UINT8_MAX =%d",coverage,UINT8_MAX);
return (double)coverage / UINT8_MAX;
}
Hi !!
I am new to Objective-C.
I am developing a tracker application, in which I have to store all the gps coordinates and then analyse them to track the path in which the vehicle was. I am using two arrays for storing the latitude and longitude points. When trying to use the Latitude points for calculations later, its giving me an error saying "Out of bound exception at index 1", but the size of the array is being shown to me as 4. Here is the code snippet of my ViewController.h, please take a look and let me know how I do this.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
[trackingMapView addPoint:newLocation];
float time = -[startDate timeIntervalSinceNow];
displayLabel.text = [NSString stringWithFormat:#"Speed: %.02f mph", distance * 2.2369 / time];
// if there was a previous location
if (oldLocation != nil)
{
// add distance from the old location tp the total distance
distance += [newLocation getDistanceFrom:oldLocation];
}
// create a region centered around the new point
MKCoordinateSpan span = MKCoordinateSpanMake(0.005, 0.005);
// create a new MKCoordinateRegion centered around the new location
MKCoordinateRegion region = MKCoordinateRegionMake(newLocation.coordinate, span);
//New Added Code
CLLocationSpeed speed = newLocation.speed ;
speed = speed * 2.2369 ;
/* if (speed > 10.000)
{
NSString *message = #"Slow Down !!" ;
UIAlertView *alertforspeed = [[UIAlertView alloc] initWithTitle:#"Speed Monitor" message: message delegate:self cancelButtonTitle:#"Return" otherButtonTitles:nil];
[alertforspeed show];
[alertforspeed release];
}*/
//NEW CODE 2
//----------------------------------------------
arraywithlat= [[NSMutableArray alloc]init];
arraywithlong= [[NSMutableArray alloc]init];
NSNumber *lati = [NSNumber numberWithFloat:newLocation.coordinate.latitude];
NSNumber *longi = [NSNumber numberWithFloat:newLocation.coordinate.longitude];
[arraywithlat addObject:lati];
[arraywithlong addObject:longi];
//[locations addObject:newLocation];
NSLog(#"Array with lat : %#" , arraywithlat);
NSLog(#"Array with long: %#", arraywithlong);
//-----------------------------------------------
int i;
float r1 = 0.0, r2, result ;
NSLog(#"IN LANE TRACKING, BEFORE IFF");
int exsize = sizeof(arraywithlat);
NSLog(#"SIze of array before the loop: %d", exsize);
if (sizeof(arraywithlat) >= 4) {
NSLog(#"CROSSED THE IFFFFF");
NSLog(#"SIXE OF ARRAYWITHLAT: %lu" , sizeof(arraywithlat));
for (i = 0; i <= sizeof(arraywithlat); i++) {
NSNumber *tla1 = [arraywithlat objectAtIndex:i];
float temp1 = [tla1 floatValue];
NSLog(#"temp111111111: %f",temp1);
NSNumber *tla2 = [arraywithlat objectAtIndex:i+1] ;
float temp2 = [tla2 floatValue];
float r1 = temp1 - temp2 ;
// float r1 = [NSNumber numberWithFloat:tla2] - [NSNumber numberWithFloat:tla1];
//float r1 = [tla2 floatValue] - [tla1 floatValue];
r1 = r1 * r1 ;
//float tla = arraywithlat objectAtIndex: i+1) - (arraywithlat objectAtIndex: i);
//float tlo = [arraywithlong obj]
// float tr1 = powf(((arraywithlat objectAtIndex:(i+1))-([arraywithlat objectatIndex:(i)])), <#float#>)
}
}
NSLog(#"r1 after cal: %f",r1);
// reposition the map t show the new point
[mapView setRegion:region animated:YES];
}
I think your problem is here:
for (i = 0; i <= sizeof(arraywithlat); i++) {
NSNumber *tla1 = [arraywithlat objectAtIndex:i];
float temp1 = [tla1 floatValue];
NSLog(#"temp111111111: %f",temp1);
NSNumber *tla2 = [arraywithlat objectAtIndex:i+1] ;
float temp2 = [tla2 floatValue];
float r1 = temp1 - temp2 ;
// float r1 = [NSNumber numberWithFloat:tla2] - [NSNumber numberWithFloat:tla1];
//float r1 = [tla2 floatValue] - [tla1 floatValue];
r1 = r1 * r1 ;
//float tla = arraywithlat objectAtIndex: i+1) - (arraywithlat objectAtIndex: i);
//float tlo = [arraywithlong obj]
// float tr1 = powf(((arraywithlat objectAtIndex:(i+1))-([arraywithlat objectatIndex:(i)])), <#float#>)
}
first indexes in array starts from 0 to n-1, so i <= sizeof(arraywithlat) here wrong, i < sizeof(arraywithlat) should be.
And NSNumber *tla2 = [arraywithlat objectAtIndex:i+1] is wrong. change it, coz when i will be equal to n-1 then you get exception out of bound. And can you explain what does this loop, may be we help you to rewrite it
Also note that - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation method of delegate is deprecated. Use locationManager:didUpdateLocations: instead.
I'm doing this sort of thing:
- (NSArray*)colors {
float divisor = .3333;
NSMutableArray *retVal = [NSMutableArray array];
for (float one=0; one <= 1.0f; one += divisor) {
for (float two = 0; two <= 1.0f; two += divisor) {
for (float three = 0; three <= 1.0f; three += divisor) {
UIColor *color = [UIColor colorWithRed:one green:two blue:three alpha:.5];
// also bad
// UIColor *color = [UIColor colorWithHue:one saturation:two brightness:three alpha:.5];
[retVal addObject:color];
}
}
}
return retVal;
}
and, as I suspected, the colors come out horribly out of order (to the eye). The reds are not with the reds, purples not with the purples, etc.
Is there no easy way to create a list of diverse colors, nicely grouped according to human criteria like, "that looks blue?"
This worked quite well. It will NOT help the fact that you have a lot of repeated colors. See below:
NSArray *sorted = [[dict allValues] sortedArrayUsingComparator:^NSComparisonResult(UIColor* obj1, UIColor* obj2) {
float hue, saturation, brightness, alpha;
[obj1 getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha];
float hue2, saturation2, brightness2, alpha2;
[obj2 getHue:&hue2 saturation:&saturation2 brightness:&brightness2 alpha:&alpha2];
if (hue < hue2)
return NSOrderedAscending;
else if (hue > hue2)
return NSOrderedDescending;
if (saturation < saturation2)
return NSOrderedAscending;
else if (saturation > saturation2)
return NSOrderedDescending;
if (brightness < brightness2)
return NSOrderedAscending;
else if (brightness > brightness2)
return NSOrderedDescending;
return NSOrderedSame;
}];
You can access the components (HSBA) like this in iOS 4.x:
CGFloat *components = (CGFloat *)CGColorGetComponents([color CGColor]);
float hue = components[0];
float saturation = components[1]; // etc. etc.
To avoid repeating colors: you can put the elements in an NSMutableDictionary, keyed on something like their hue-saturation-brightness (each rounded to the nearest .10)... then you get the array from THAT, and then sort.
I think using [UIColor colorWithHue:saturation: brightness: alpha:] is your best bet. If you fix saturation, brightness and alpha and just use Hue you'll get all the colours in order.
Check out the wiki for HSB for more information.
for (float hsb = 0; hsb <= 1.0f; hsb += divisor) {
UIColor *color = [UIColor colorWithHue:hsb saturation:1.0f brightness:1.0f alpha:.5f];
[retVal addObject:color];
}