117

I am trying to convert this Obj-C code to Swift code but I don't know what the equivalent of this code should be ?

#define DEGREES_TO_RADIANS(degrees)((M_PI * degrees)/180)

I googled and found this

But I don't understand how to convert that in Swift in my case?

Community
  • 1
  • 1
Dharmesh Kheni
  • 71,228
  • 33
  • 160
  • 165

10 Answers10

285

Xcode 11 • Swift 5.1 or later

extension BinaryInteger {
    var degreesToRadians: CGFloat { CGFloat(self) * .pi / 180 }
}

extension FloatingPoint {
    var degreesToRadians: Self { self * .pi / 180 }
    var radiansToDegrees: Self { self * 180 / .pi }
}

Playground

45.degreesToRadians         // 0.7853981633974483

Int(45).degreesToRadians    // 0.7853981633974483
Int8(45).degreesToRadians   // 0.7853981633974483
Int16(45).degreesToRadians  // 0.7853981633974483
Int32(45).degreesToRadians  // 0.7853981633974483
Int64(45).degreesToRadians  // 0.7853981633974483

UInt(45).degreesToRadians   // 0.7853981633974483
UInt8(45).degreesToRadians  // 0.7853981633974483
UInt16(45).degreesToRadians // 0.7853981633974483
UInt32(45).degreesToRadians // 0.7853981633974483
UInt64(45).degreesToRadians // 0.7853981633974483

Double(45).degreesToRadians    // 0.7853981633974483
CGFloat(45).degreesToRadians   // 0.7853981633974483
Float(45).degreesToRadians     // 0.7853981
Float80(45).degreesToRadians   // 0.78539816339744830963

If you would like to make the binary integer return a floating point type instead of always returning a CGFloat you can make a generic method instead of a computed property:

extension BinaryInteger {
    func degreesToRadians<F: FloatingPoint>() -> F {  F(self) * .pi / 180 }
}

let radiansDouble: Double = 45.degreesToRadians()   // 0.7853981633974483
let radiansCGFloat: CGFloat = 45.degreesToRadians() // 0.7853981633974483
let radiansFloat: Float = 45.degreesToRadians()     // 0.7853981
let radiansFloat80: Float80 = 45.degreesToRadians() // 0.78539816339744830963
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
72

This is not identically what you asked, but in Swift 3 / iOS 10, you can use the Measurement type and do the conversion without knowing the formula!

let result = Measurement(value: 45, unit: UnitAngle.degrees)
    .converted(to: .radians).value
matt
  • 515,959
  • 87
  • 875
  • 1,141
43

Apple provides these GLKit functions for conversion:

func GLKMathDegreesToRadians(_ degrees: Float) -> Float
func GLKMathRadiansToDegrees(_ radians: Float) -> Float
Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • 1
    GLKit is going off stage soon, since now it is Metal's world, bye GL. As porting iPad app to Mac, GLKit is not longer supported!. – Juguang May 14 '20 at 15:52
19
let angle = 45° // angle will be in radians, 45 is in degrees

Compiles under Swift 3. Still keep all values, do all calculations in radians with CGFloats..., but make the code more readable with the constants in degrees. For example: 90° The ° sign will magically do the degrees to radians conversion.

How to set this up:

Define and use a postfix operator for the ° sign. This operator will do the conversion from degrees to radians. This example is for Ints, extend these also for the Float types if you have the need.

postfix operator °

protocol IntegerInitializable: ExpressibleByIntegerLiteral {
  init (_: Int)
}

extension Int: IntegerInitializable {
  postfix public static func °(lhs: Int) -> CGFloat {
    return CGFloat(lhs) * .pi / 180
  }
}

Some examples of usage:

let angle = 45°

contentView.transform = CGAffineTransform(rotationAngle: 45°)

let angle = 45
contentView.transform = CGAffineTransform(rotationAngle: angle°)

Warning!

It is too easy to use this conversion twice (on a value already in radians by mistake), you will get a very small number as the result, and seemingly the resulting angle will be always zero... DO NOT use ° on the same value twice (do not convert twice)!!:

// OBVIOUSLY WRONG!
let angle = 45°° // ° used twice here

// WRONG! BUT EASY TO MISS
let angle = 45° // ° used here
contentView.transform = CGAffineTransform(rotationAngle: angle°) // ° also used here
t1ser
  • 1,574
  • 19
  • 18
  • `IntegerInitializable` protocol is pointless. Int already conforms to `ExpressibleByIntegerLiteral` – Leo Dabus May 26 '20 at 05:28
  • Btw You can make this operator generic to support any Integer and return any Floating Point as well. `public extension FixedWidthInteger { postfix static func °(lhs: Self) -> T { T(lhs) * .pi / 180 } }` – Leo Dabus May 26 '20 at 05:32
  • And before you comment about it when creating a generic method you would need to explicitly set the resulting type if the compiler cannot infer the resulting type `let angle: CGFloat = 45°`. But it is not needed where it expects a `CGFloat` or any `FloatingPoint` type `CGAffineTransform(rotationAngle: 45°)` – Leo Dabus May 26 '20 at 05:42
12

Also, to convert fron radians to degrees (if anyone stumbles upon this on google):

var degrees = radians * (180.0 / M_PI)
nikans
  • 2,468
  • 1
  • 28
  • 35
5

The most efficient and accurate way to convert a Double between degrees and radians:

import Foundation

extension Double {
    var radians: Double { return Measurement(value: self, unit: UnitAngle.degrees).converted(to: UnitAngle.radians).value }
    var degrees: Double { return Measurement(value: self, unit: UnitAngle.radians).converted(to: UnitAngle.degrees).value }
}

The Foundation team at Apple addressed this problem long ago by developing a solution that surpasses all others:

Measurements and Units - WWDC 2016, Session 238

James Bush
  • 1,485
  • 14
  • 19
3

You're no longer limited to ASCII characters when creating variable names, so how about this using π (alt-p):

typealias RadianAngle = CGFloat

let π = RadianAngle(M_PI)
let π_x_2 = RadianAngle(M_PI * 2)
let π_2 = RadianAngle(M_PI_2)
let π_4 = RadianAngle(M_PI_4)

extension RadianAngle {
    var degrees: CGFloat {
        return self * 180 / π
    }
    init(degrees: Int) {
        self = CGFloat(degrees) * π / 180
    }
}

Example usage:

let quarterCircle = RadianAngle(degrees: 90)
print("quarter circle = \(quarterCircle) radians")
// quarter circle = 1.5707963267949 radians

let halfCircle = π
print("half circle = \(halfCircle.degrees) degrees")
// half circle = 180.0 degrees
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
2

Swift 4.2

You can write global generic functions:

public func radians<T: FloatingPoint>(degrees: T) -> T {
    return .pi * degrees / 180
}

public func degrees<T: FloatingPoint>(radians: T) -> T {
    return radians * 180 / .pi
}

Example:

let cgFloat: CGFloat = 23.0
let double: Double = 23.0
let float: Float = 23.0
let int: Int = 23

let cgf = radians(degrees: cgFloat) // 0.4014 CGFloat
let d = radians(degrees: double)    // 0.4014 Double
let f = radians(degrees: float)     // 0.4014 Float
let i = radians(degrees: int)       // compile error

In case when you don't want to extension all your floating types like this

extension FloatingPoint { ... }
Tikhonov Aleksandr
  • 13,945
  • 6
  • 39
  • 53
  • Why not simply `return .pi * degrees / 180` ? – Leo Dabus Mar 01 '19 at 13:22
  • @LeoDabus because `T` is `FloatingPoint` type and 180 is `Int` type. In swift you cannot do calculations on different types. – Tikhonov Aleksandr Mar 01 '19 at 14:12
  • You are wrong. Swift can infer the type. It behaves exactly the same as if you were extending the FloatingPoint type. 180 can be any thing. not necessarily an Int – Leo Dabus Mar 01 '19 at 14:13
  • The same applies to your `.pi` you don't need to call `T.pi` because the compiler knows you are returning `T` so it infers that `.pi`is a static property on `T` (which represents a `FloatingPoint` in this case). – Leo Dabus Mar 01 '19 at 14:23
  • Swift knows that `.pi` is `T`, because `FloatingPoint` implements `.pi` and 180 by default is `Int`. In Swift you cannot do calculations `Double` with `Int`, `Float` with `Int`, `FloatingPoint` with `Int`, you need to cast 180 to generic `FloatingPoint` type in order to get valid expression. – Tikhonov Aleksandr Mar 01 '19 at 14:46
  • Why don’t you simply test it in a playground. Anyway fell free to keep your answer as you wish if you insist that T initializer is needed there. – Leo Dabus Mar 01 '19 at 14:47
  • You are right, it works for .pi * degrees / 180 (180 is `Int`), but it doesn't work for .pi * degrees / 180.0 (180.0 is `Double`). It looks like Swift can infer how to cast `Int` 180, and Swift cannot infer how to cast `Double` 180.0 to `FloatingPoint` – Tikhonov Aleksandr Mar 01 '19 at 14:53
  • 1
    FloatingPoint conforms to ExpressibleByIntegerLiteral that’s why you can write 180 there – Leo Dabus Mar 01 '19 at 14:56
  • 1
    180.0 needs T because T is not necessarily a Double. It can be Any floating point type. You initialize a new one from your double 180.0 – Leo Dabus Mar 01 '19 at 14:57
  • Btw check this `extension Int: ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { self.init(value) } } let integer: Int = 2.5 // 2` – Leo Dabus Mar 01 '19 at 15:03
  • Yep, `Int.init(floatLiteral: 2.5)` is similar to write `Int.init(2.5)`, in first and second case parameter is `BinaryFloatingPoint` – Tikhonov Aleksandr Mar 01 '19 at 15:09
1

I'd use the same principle @t1ser mentioned above, but create an extension of CGFloat to make it easier to use decimals for the degree as well (so you could have a 23.4 degrees, for example):

extension CGFloat {
    func degrees() -> CGFloat {
        return self * .pi / 180
    }

    init(degrees: CGFloat) {
        self = degrees.degrees()
    }
}

Using it would be pretty easy as well (mainly because I personally didn't know how to type ° - in case you didn't either, it's option+shift+8 btw):

let degreesToConvert: CGFloat = 45.7
.
.
.
let convertedDegrees = degreesToConvert.degrees()

Or to use the initialiser:

let convertedDegrees = CGFloat(degrees: 45.3)
Septronic
  • 1,156
  • 13
  • 32
0

If it is ok to import SwiftUI or if you are already using it you could use the Angle type to convert between angle units, eg.:

import SwiftUI

let radians = Angle(degrees: 45).radians

This will convert from Double to Double.

nylki
  • 498
  • 5
  • 15