CarpenterBlood
CarpenterBlood

Reputation: 151

How should this be translate from Objective C to swift 3 correctly

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

Answers (1)

Rob
Rob

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

Related Questions