Reputation: 151
First off I'm very new to any programing language. So be gentle. I am trying to translate this code in objective C to swift 3
NSArray *points = @[[NSValue valueWithCGPoint:rectangleFeature.topLeft],[NSValue valueWithCGPoint:rectangleFeature.topRight],[NSValue valueWithCGPoint:rectangleFeature.bottomLeft],[NSValue valueWithCGPoint:rectangleFeature.bottomRight]];
CGPoint min = [points[0] CGPointValue];
CGPoint max = min;
for (NSValue *value in points)
{
CGPoint point = value.CGPointValue;
min.x = fminf(point.x, min.x);
min.y = fminf(point.y, min.y);
max.x = fmaxf(point.x, max.x);
max.y = fmaxf(point.y, max.y);
}
CGPoint center =
{
0.5f * (min.x + max.x),
0.5f * (min.y + max.y),
};
NSNumber *(^angleFromPoint)(id) = ^(NSValue *value)
{
CGPoint point = value.CGPointValue;
CGFloat theta = atan2f(point.y - center.y, point.x - center.x);
CGFloat angle = fmodf(M_PI - M_PI_4 + theta, 2 * M_PI);
return @(angle);
};
NSArray *sortedPoints = [points sortedArrayUsingComparator:^NSComparisonResult(id a, id b)
{
return [angleFromPoint(a) compare:angleFromPoint(b)];
}];
So far I have been able to get it down to this in swift 3
let points: NSArray = [NSValue(cgPoint: rectangleFeature!.topLeft), NSValue(cgPoint: rectangleFeature!.topRight), NSValue(cgPoint: rectangleFeature!.bottomLeft), NSValue(cgPoint: rectangleFeature!.bottomRight)]
var min: CGPoint = (points[0] as! NSValue).cgPointValue
var max: CGPoint = min
for value in points {
let point: CGPoint = (value as! NSValue).cgPointValue
min.x = CGFloat(fminf(Float(point.x), Float(min.x)))
min.y = CGFloat(fminf(Float(point.y), Float(min.y)))
max.x = CGFloat(fmaxf(Float(point.x), Float(max.x)))
max.y = CGFloat(fmaxf(Float(point.y), Float(max.y)))
}
var center: CGPoint = [0.5 * (min.x + max.x), 0.5 * (min.y + max.y)]
var angleFromPoint: ((Any) -> NSNumber)?? = {(value: NSValue) in
var point: CGPoint = value.cgPointValue
var theta: CGFloat = atan2f(point.y - center.y, point.x - center.x)
var angle: CGFloat = fmodf(.pi - M_PI_4 + theta, 2 * .pi)
return angle
}
var sortedPoints: NSArray = [(points).sortedArray(comparator: {(a: Any, b: Any) -> ComparisonResult in
return angleFromPoint(a).compare(angleFromPoint(b))
})]
The problem is the variable center is throwing a complier error "Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions" Now I have tried every method I can think of or could find in a google search and I have no clue as to how to resolve this. I am still very green. Next problem is the variable angleFromPoint is throwing a complier error "Cannot convert value of type '(NSValue) ->_' to specified type '((Any) -> NSNumber)??" I also have no clue how to resolve this. I have tried casting and force casting with no luck and probably not the best practice either. The final problem is in the variable sortedPoints throwing a complier error " Value of optional type '((Any) -> NSNumber)?? not unwrapped; did you mean to use '!' or '?'?" on the return angleFromPoint(a).compare(angleFromPoint(b)). Now I have tried to unwrap the optional value also with no luck. It just keeps saying the same error when I do. I acquired this objective C code from here so please have a look there to understand what I need it for. It works perfectly in objective C however I have no understanding whatsoever in objective C and I may just need to resort to creating an objective c class to put it in and call it from swift, although I'd like it to be in swift so I can maintain it and maybe improve upon if needed. Thanks for any help. Hopefully someone smarter than me can make sense of this and shed some light on it.
Upvotes: 2
Views: 374
Reputation: 437482
If you're going to use Swift, I'd retire the use of NSArray
and use Swift arrays. You can then have a simple array of CGPoint
values, eliminating all of this NSValue
silliness we had to do in Objective-C:
let points = [
rectangleFeature.topLeft,
rectangleFeature.topRight,
rectangleFeature.bottomLeft,
rectangleFeature.bottomRight
]
Then you can iterate through that array directly:
var minimum = points[0]
var maximum = points[0]
for point in points {
minimum.x = min(minimum.x, point.x)
minimum.y = min(minimum.y, point.y)
maximum.x = max(maximum.x, point.x)
maximum.y = max(maximum.y, point.y)
}
(Theoretically you could use reduce
, but it's just as easy to use the for
loop like your example. Also, I spelled out the minimum
and maximum
variables to avoid any possible confusion with the functions of the same name.)
Then, you can define your angleFromPoint
to just return CGFloat
rather than messing around with NSNumber
:
let center = CGPoint(x: (minimum.x + maximum.x) / 2, y: (minimum.y + maximum.y) / 2)
let angle = { (point: CGPoint) -> CGFloat in
let theta = atan2(point.y - center.y, point.x - center.x)
return fmod(.pi * 3.0 / 4.0 + theta, 2 * .pi)
}
Finally, because your closure now returns simple numeric type, you can use the more intuitive <
operator, rather than .compare
:
let sortedPoints = points.sorted { angle($0) < angle($1) }
So, pulling that all together:
let points = [
rectangleFeature.topLeft,
rectangleFeature.topRight,
rectangleFeature.bottomLeft,
rectangleFeature.bottomRight
]
var minimum = points[0]
var maximum = points[0]
for point in points {
minimum.x = min(minimum.x, point.x)
minimum.y = min(minimum.y, point.y)
maximum.x = max(maximum.x, point.x)
maximum.y = max(maximum.y, point.y)
}
let center = CGPoint(x: (minimum.x + maximum.x) / 2, y: (minimum.y + maximum.y) / 2)
let angle = { (point: CGPoint) -> CGFloat in
let theta = atan2(point.y - center.y, point.x - center.x)
return fmod(.pi * 3.0 / 4.0 + theta, 2 * .pi)
}
let sortedPoints = points.sorted { angle($0) < angle($1) }
Upvotes: 4