How to display fraction text number over another? - swift

I need to display a fraction in my app but cannot find a good way to do it?
Should looks something like this
(proof of concept... don't need the font):
There is other posts similar to this but they are in ObJ C, I can not find a reliable solution in swift

This is just Apple sample code from the Introducing the New System Fonts video from WWDC 2015 put into a playground and using a UILabel to render plain text fractions using font features. [Updated for Swift 4]
//: Playground - noun: a place where people can play
import UIKit
import CoreGraphics
let pointSize : CGFloat = 60.0
let systemFontDesc = UIFont.systemFont(ofSize: pointSize,
weight: UIFont.Weight.light).fontDescriptor
let fractionFontDesc = systemFontDesc.addingAttributes(
[
UIFontDescriptor.AttributeName.featureSettings: [
[
UIFontDescriptor.FeatureKey.featureIdentifier: kFractionsType,
UIFontDescriptor.FeatureKey.typeIdentifier: kDiagonalFractionsSelector,
],
]
] )
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
label.font = UIFont(descriptor: fractionFontDesc, size:pointSize)
label.text = "12/48" // note just plain numbers and a regular slash
Just tap on the eye in the playground and you will see a beautiful fraction.
Introducing the New System Fonts (WWDC 2015 at 20:24)

I had to do something similar in an App. I created a mapping between common fractions and the related unicode characters like so:
enum Fraction: Double {
case Eighth = 0.125
case Quarter = 0.25
case Third = 0.333333333333333
case Half = 0.5
case TwoThirds = 0.666666666666667
case ThreeQuarters = 0.75
}
func localizedStringFromFraction(fraction: Fraction) -> String {
switch fraction {
case .Eighth:
return NSLocalizedString("\u{215B}", comment: "Fraction - 1/8")
case .Quarter:
return NSLocalizedString("\u{00BC}", comment: "Fraction - 1/4")
case .Third:
return NSLocalizedString("\u{2153}", comment: "Fraction - 1/3")
case .Half:
return NSLocalizedString("\u{00BD}", comment: "Fraction - 1/2")
case .TwoThirds:
return NSLocalizedString("\u{2154}", comment: "Fraction - 2/3")
case .ThreeQuarters:
return NSLocalizedString("\u{00BE}", comment: "Fraction - 3/4")
}
}
If you need support for more fractions, you can find the mapping here.

#KennethBruno's answer works but I find it a little inelegant. Here's my version:
let superscriptDigits = Array("⁰¹²³⁴⁵⁶⁷⁸⁹".characters)
let subscriptDigits = Array("₀₁₂₃₄₅₆₇₈₉".characters)
func vulgarFractionWithNumerator(numerator: UInt, denominator: UInt) -> String {
let zeroBias = UnicodeScalar("0").value
let supers = "\(numerator)".unicodeScalars.map { superscriptDigits[Int($0.value - zeroBias)] }
let subs = "\(denominator)".unicodeScalars.map { subscriptDigits[Int($0.value - zeroBias)] }
return String(supers + [ "⁄" ] + subs)
}
vulgarFractionWithNumerator(123, denominator: 45678)
Result:
"¹²³⁄₄₅₆₇₈"

Here is a Swift solution for arbitrary fractions:
enum ScriptType {
case Superscript
case Subscript
}
func createSuperOrSubscriptDigit(character:Character, type:ScriptType) -> Character {
switch character {
case "0": return type == .Superscript ? "\u{2070}" : "\u{2080}"
case "1": return type == .Superscript ? "\u{00b9}" : "\u{2081}"
case "2": return type == .Superscript ? "\u{00b2}" : "\u{2082}"
case "3": return type == .Superscript ? "\u{00b3}" : "\u{2083}"
case "4": return type == .Superscript ? "\u{2074}" : "\u{2084}"
case "5": return type == .Superscript ? "\u{2075}" : "\u{2085}"
case "6": return type == .Superscript ? "\u{2076}" : "\u{2086}"
case "7": return type == .Superscript ? "\u{2077}" : "\u{2087}"
case "8": return type == .Superscript ? "\u{2078}" : "\u{2088}"
case "9": return type == .Superscript ? "\u{2079}" : "\u{2089}"
default: return character
}
}
func createSuperOrSubscriptDigits(string:String, type:ScriptType) -> String {
return String(string.characters.map() { createSuperOrSubscriptDigit($0, type: type) })
}
extension String {
func createSuperscriptDigits() -> String {
return createSuperOrSubscriptDigits(self, type: .Superscript)
}
func createSubscriptDigits() -> String {
return createSuperOrSubscriptDigits(self, type: .Subscript)
}
}
func fractionString(numerator:String, denominator:String) -> String {
return numerator.createSuperscriptDigits() + "\u{2044}" + denominator.createSubscriptDigits()
}

Related

Swift NSPopUpButton enum

I'm implementing an NSPopUpButton (for a macOS app using Swift), as in the picture:
And, I have the following code, which actually works:
enum Importance: Int8 {
case EXTREMELY_IMPORTANT = 5
case VERY_IMPORTANT = 4
case IMPORTANT = 3
case NORMAL = 2
case NOT_IMPORTANT = 1
case JUST_FOR_RECORD = 0
case ERROR = -1
}
let english_extremely_important = "Extremely Important"
let english_very_important = "Very Important"
let english_important = "Important"
let english_normal = "Normal"
let english_not_important = "Not Important"
let english_just_for_record = "Just for Record"
var importanceEnglishItems: [String] = {
return [
english_extremely_important,
english_very_important,
english_important,
english_normal,
english_not_important,
english_just_for_record
]
}()
func getImportance(importanceEnglish: String) -> Int8 {
switch importanceEnglish {
case english_extremely_important:
return Importance.EXTREMELY_IMPORTANT.rawValue
case english_very_important:
return Importance.VERY_IMPORTANT.rawValue
case english_important:
return Importance.IMPORTANT.rawValue
case english_normal:
return Importance.NORMAL.rawValue
case english_not_important:
return Importance.NOT_IMPORTANT.rawValue
case english_just_for_record:
return Importance.JUST_FOR_RECORD.rawValue
default:
return Importance.ERROR.rawValue
}
}
Whenever the user selects the item in the popup menu, this code executes:
#IBAction func handleImportancePopUpButtonSelectionChanged(_ importancePopUpButton: NSPopUpButton) {
let importanceIndex = getImportance(importanceEnglish: importancePopUpButton.titleOfSelectedItem!)
print("importanceIndex: \(importanceIndex)")
}
It works, BUT... I believe this implementation isn't that elegant. What is the better way to do this?
I have these requirements in mind:
The corresponding values of the enums list "enum Importance: Int8" are fixed. For example, EXTREMELY_IMPORTANT must be 5, as it is already coded on the server-side. Therefore, based on user's selection, the corresponding enum values must be sent to be server. (EXTREMELY_IMPORTANT == 5, etc.)
Further to the above point, the selection's index of the NSPopUpButton cannot be used for sending to the server. For example, "Extremely Important" would be 0 since it is the first one on the top of the list.
The NSPopUpButton is using "titleOfSelectedItem" and then call getImportance(importanceEnglish: String) method, which is inefficient, and should be better off using "indexOfSelectedItem" instead. That means, it would be more efficient to use the selection index of "Extremely Important" (which is 0) to retrieve the value of 5 for sending to the server.
Better yet, if everything can support support localization (more languages: Japanese, etc.) using standard practice.
How can I make my Swift code more beautiful?
I would change the encapsulation a little bit to make it more readable; such solution would be a better way to start with in my view, (e.g. adding localisation or extending it by new values, etc...).
this idea is obviously not the only way – there are many other alterations/solutions could be as good as this (or maybe even better).
Swift 4.2
enum Importance: Int, CaseIterable {
case extremelyImportant = 5
case veryImportant = 4
case important = 3
case normal = 2
case notImportant = 1
case justForRecord = 0
var friendlyName: String? {
switch self {
case .extremelyImportant: return "Extremely Important"
case .veryImportant: return "Very Important"
case .important: return "Important"
case .notImportant: return "Not Important"
case .justForRecord: return "Just for Record"
default: return nil
}
}
init?(withName name: String) {
guard let importance = Importance.allCases.first(where: {
guard let friendlyName = $0.friendlyName else { return false }
return friendlyName == name
}) else { return nil }
self = importance
}
static var allCasesNames: [String] {
return Importance.allCases.compactMap { $0.friendlyName }
}
}
You can create NSMenuItem with a Title and importance as tag and add it NSPopUpButton.menu.items.
override func viewDidLoad() {
super.viewDidLoad()
popUpButton.menu?.items = self.importanceEnglishItems
}
class func MenuItem(title: String, tag: Int) -> NSMenuItem {
let item = NSMenuItem(title: title, action: nil, keyEquivalent: "")
item.tag = tag
return item
}
var importanceEnglishItems: [NSMenuItem] = {
return [
MenuItem(title: "Extremely Important", tag: 5),
MenuItem(title: "Very Important", tag: 4),
MenuItem(title: "Important", tag: 3),
MenuItem(title: "Normal", tag: 2),
MenuItem(title: "Not Important", tag: 1),
MenuItem(title: "Just for Record", tag: 0)
]
}()
#IBAction func handleSelection(_ sender: NSPopUpButton) {
guard let item = sender.selectedItem else { return }
print("importanceIndex: \(item.tag)")
}

How to execute multiplications and/or divisions in the right order?

I am doing a simple calculator, but when performing the multiplication and division, my code doesn't make them a priority over plus and minus.
When doing -> 2 + 2 * 4, result = 16 instead of 10...
How to conform to the math logic inside my switch statement?
mutating func calculateTotal() -> Double {
var total: Double = 0
for (i, stringNumber) in stringNumbers.enumerated() {
if let number = Double(stringNumber) {
switch operators[i] {
case "+":
total += number
case "-":
total -= number
case "÷":
total /= number
case "×":
total *= number
default:
break
}
}
}
clear()
return total
}
Assuming you want a generalised and perhaps extensible algorithm for any arithmetic expression, the right way to do this is to use the Shunting Yard algorithm.
You have an input stream, which is the numbers and operators as the user typed them in and you have an output stream, which is the same numbers and operators but rearranged into reverse Polish notation. So, for example 2 + 2 * 4 would be transformed into 2 2 4 * + which is easily calculated by putting the numbers on a stack as you read them and applying the operators to the top items on the stack as you read them.
To do this the algorithm has an operator stack which can be visualised as a siding (hence "shunting yard") into which low priority operators are shunted until they are needed.
The general algorithm is
read an item from the input
if it is a number send it to the output
if the number is an operator then
while the operator on the top of the stack is of higher precedence than the operator you have pop the operator on the stack and send it to the output
push the operator you read from input onto the stack
repeat the above until the input is empty
pop all the operators on the stack into the output
So if you have 2 + 2 * 4 (NB top of the stack is on the left, bottom of the stack is on the right)
start:
input: 2 + 2 * 4
output: <empty>
stack: <empty>
step 1: send the 2 to output
input: + 2 * 4
output: 2
stack: <empty>
step 2: stack is empty so put + on the stack
input: 2 * 4
output: 2
stack: +
step 3: send the 2 to output
input: * 4
output: 2 2
stack: +
step 4: + is lower priority than * so just put * on the stack
input: 4
output: 2 2
stack: * +
step 5: Send 4 to output
input:
output: 2 2 4
stack: * +
step 6: Input is empty so pop the stack to output
input:
output: 2 2 4 * +
stack:
The Wikipedia entry I linked above has a more detailed description and an algorithm that can handle parentheses and function calls and is much more extensible.
For completeness, here is an implementation of my simplified version of the algorithm
enum Token: CustomStringConvertible
{
var description: String
{
switch self
{
case .number(let num):
return "\(num)"
case .op(let symbol):
return "\(symbol)"
}
}
case op(String)
case number(Int)
var precedence: Int
{
switch self
{
case .op(let symbol):
return Token.precedences[symbol] ?? -1
default:
return -1
}
}
var operation: (inout Stack<Int>) -> ()
{
switch self
{
case .op(let symbol):
return Token.operations[symbol]!
case .number(let value):
return { $0.push(value) }
}
}
static let precedences = [ "+" : 10, "-" : 10, "*" : 20, "/" : 20]
static let operations: [String : (inout Stack<Int>) -> ()] =
[
"+" : { $0.push($0.pop() + $0.pop()) },
"-" : { $0.push($0.pop() - $0.pop()) },
"*" : { $0.push($0.pop() * $0.pop()) },
"/" : { $0.push($0.pop() / $0.pop()) }
]
}
struct Stack<T>
{
var values: [T] = []
var isEmpty: Bool { return values.isEmpty }
mutating func push(_ n: T)
{
values.append(n)
}
mutating func pop() -> T
{
return values.removeLast()
}
func peek() -> T
{
return values.last!
}
}
func shuntingYard(input: [Token]) -> [Token]
{
var operatorStack = Stack<Token>()
var output: [Token] = []
for token in input
{
switch token
{
case .number:
output.append(token)
case .op:
while !operatorStack.isEmpty && operatorStack.peek().precedence >= token.precedence
{
output.append(operatorStack.pop())
}
operatorStack.push(token)
}
}
while !operatorStack.isEmpty
{
output.append(operatorStack.pop())
}
return output
}
let input: [Token] = [ .number(2), .op("+"), .number(2), .op("*"), .number(4)]
let output = shuntingYard(input: input)
print("\(output)")
var dataStack = Stack<Int>()
for token in output
{
token.operation(&dataStack)
}
print(dataStack.pop())
If you only have the four operations +, -, x, and ÷, you can do this by keeping track of a pendingOperand and pendingOperation whenever you encounter a + or -.
Then compute the pending operation when you encounter another + or -, or at the end of the calculation. Note that + or - computes the pending operation, but then immediately starts a new one.
I have modified your function to take the stringNumbers, operators, and initial values as input so that it could be tested independently in a Playground.
func calculateTotal(stringNumbers: [String], operators: [String], initial: Double) -> Double {
func performPendingOperation(operand: Double, operation: String, total: Double) -> Double {
switch operation {
case "+":
return operand + total
case "-":
return operand - total
default:
return total
}
}
var total = initial
var pendingOperand = 0.0
var pendingOperation = ""
for (i, stringNumber) in stringNumbers.enumerated() {
if let number = Double(stringNumber) {
switch operators[i] {
case "+":
total = performPendingOperation(operand: pendingOperand, operation: pendingOperation, total: total)
pendingOperand = total
pendingOperation = "+"
total = number
case "-":
total = performPendingOperation(operand: pendingOperand, operation: pendingOperation, total: total)
pendingOperand = total
pendingOperation = "-"
total = number
case "÷":
total /= number
case "×":
total *= number
default:
break
}
}
}
// Perform final pending operation if needed
total = performPendingOperation(operand: pendingOperand, operation: pendingOperation, total: total)
// clear()
return total
}
Tests:
// 4 + 3
calculateTotal(stringNumbers: ["3"], operators: ["+"], initial: 4)
7
// 4 × 3
calculateTotal(stringNumbers: ["3"], operators: ["×"], initial: 4)
12
// 2 + 2 × 4
calculateTotal(stringNumbers: ["2", "4"], operators: ["+", "×"], initial: 2)
10
// 2 × 2 + 4
calculateTotal(stringNumbers: ["2", "4"], operators: ["×", "+"], initial: 2)
8
// 17 - 2 × 3 + 10 + 7 ÷ 7
calculateTotal(stringNumbers: ["2", "3", "10", "7", "7"], operators: ["-", "×", "+", "+", "÷"], initial: 17)
22
First you have to search in the array to see if there is a ÷ or × sign.
Than you can just sum or subtract.
mutating func calculateTotal() -> Double {
var total: Double = 0
for (i, stringNumber) in stringNumbers.enumerated() {
if let number = Double(stringNumber) {
switch operators[i] {
case "÷":
total /= number
case "×":
total *= number
default:
break
}
//Remove the number from the array and make another for loop with the sum and subtract operations.
}
}
clear()
return total
}
This will work if you are not using complex numbers.
If you don't care speed, as it's running by a computer and you may use the machine way to handle it. Just pick one feasible calculate to do it and then repeat until every one is calculated.
Just for fun here. I use some stupid variable and function names.
func evaluate(_ values: [String]) -> String{
switch values[1] {
case "+": return String(Int(values[0])! + Int(values[2])!)
case "-": return String(Int(values[0])! - Int(values[2])!)
case "×": return String(Int(values[0])! * Int(values[2])!)
case "÷": return String(Int(values[0])! / Int(values[2])!)
default: break;
}
return "";
}
func oneTime(_ string: inout String, _ strings: [String]) throws{
if let first = try NSRegularExpression(pattern: "(\\d+)\\s*(\(strings.map{"\\\($0)"}.joined(separator: "|")))\\s*(\\d+)", options: []).firstMatch(in: string , options: [], range: NSMakeRange(0, string.count)) {
let tempResult = evaluate((1...3).map{ (string as NSString).substring(with: first.range(at: $0))})
string.replaceSubrange( Range(first.range(at: 0), in: string)! , with: tempResult)
}
}
func recursive(_ string: inout String, _ strings: [String]) throws{
var count : Int!
repeat{ count = string.count ; try oneTime(&string, strings)
} while (count != string.count)
}
func final(_ string: inout String, _ strings: [[String]]) throws -> String{
return try strings.reduce(into: string) { (result, signs) in
try recursive(&string, signs)
}}
var string = "17 - 23 + 10 + 7 ÷ 7"
try final(&string, [["×","÷"],["+","-"]])
print("result:" + string)
Using JeremyP method and the Shunting Yard algorithm was the way that worked for me, but I had some differences that had to do with the Operator Associativity(left or right priority) so I had to work with it and I developed the code, which is based on JeremyP answer but uses arrays.
First we have the array with the calculation in Strings, e.g.:
let testArray = ["10","+", "5", "*" , "4", "+" , "10", "+", "20", "/", "2"]
We use the function below to get the RPN version using the Shunting Yard algorithm.
func getRPNArray(calculationArray: [String]) -> [String]{
let c = calculationArray
var myRPNArray = [String]()
var operandArray = [String]()
for i in 0...c.count - 1 {
if c[i] != "+" && c[i] != "-" && c[i] != "*" && c[i] != "/" {
//push number
let number = c[i]
myRPNArray.append(number)
} else {
//if this is the first operand put it on the opStack
if operandArray.count == 0 {
let firstOperand = c[i]
operandArray.append(firstOperand)
} else {
if c[i] == "+" || c[i] == "-" {
operandArray.reverse()
myRPNArray.append(contentsOf: operandArray)
operandArray = []
let uniqOperand = c[i]
operandArray.append(uniqOperand)
} else if c[i] == "*" || c[i] == "/" {
let strongOperand = c[i]
//If I want my mult./div. from right(eg because of parenthesis) the line below is all I need
//--------------------------------
// operandArray.append(strongOperand)
//----------------------------------
//If I want my mult./div. from left
let lastOperand = operandArray[operandArray.count - 1]
if lastOperand == "+" || lastOperand == "-" {
operandArray.append(strongOperand)
} else {
myRPNArray.append(lastOperand)
operandArray.removeLast()
operandArray.append(strongOperand)
}
}
}
}
}
//when I have no more numbers I append the reversed operant array
operandArray.reverse()
myRPNArray.append(contentsOf: operandArray)
operandArray = []
print("RPN: \(myRPNArray)")
return myRPNArray
}
and then we enter the RPN array in the function below to calculate the result. In every loop we remove the numbers and the operand used before and we import the previous result and two "p" in the array so in the end we are left with the solution and an array of "p".
func getResultFromRPNarray(myArray: [String]) -> Double {
var a = [String]()
a = myArray
print("a: \(a)")
var result = Double()
let n = a.count
for i in 0...n - 1 {
if n < 2 {
result = Double(a[0])!
} else {
if a[i] == "p" {
//Do nothing else. Calculations are over and the result is in your hands!!!
} else {
if a[i] == "+" {
result = Double(a[i-2])! + Double(a[i-1])!
a.insert(String(result), at: i-2)
a.remove(at: i - 1)
a.remove(at: i - 1)
a.remove(at: i - 1)
a.insert("p", at: 0)
a.insert("p", at: 0)
} else if a[i] == "-" {
result = Double(a[i-2])! - Double(a[i-1])!
a.insert(String(result), at: i-2)
a.remove(at: i - 1)
a.remove(at: i - 1)
a.remove(at: i - 1)
a.insert("p", at: 0)
a.insert("p", at: 0)
} else if a[i] == "*" {
result = Double(a[i-2])! * Double(a[i-1])!
a.insert(String(result), at: i-2)
a.remove(at: i - 1)
a.remove(at: i - 1)
a.remove(at: i - 1)
a.insert("p", at: 0)
a.insert("p", at: 0)
} else if a[i] == "/" {
result = Double(a[i-2])! / Double(a[i-1])!
a.insert(String(result), at: i-2)
a.remove(at: i - 1)
a.remove(at: i - 1)
a.remove(at: i - 1)
a.insert("p", at: 0)
a.insert("p", at: 0)
} else {
// it is a number so do nothing and go the next one
}
}//no over yet
}//n>2
}//iterating
return result
}//Func

Sum integers from a key value until there is one-digit

I need a function that both reduces integers to a single digit and accepts strings. Variants using reduce would also be appreciated.
let dict: [Character:Int] = ["a": 1, "j":1, "s":1, "b":2, "k":2, "t":2, "c":3, "l":3, "u":3, "d":4, "m":4, "v":4, "e":5, "n":5, "w":5, "f":6, "o":6, "x":6, "g":7, "p":7, "y":7, "h":8, "q":8, "z":8, "i":9, "r":9]
This function accepts strings and reduces by summing, but does not reduce to a single digit.
func f1(_ str: String) -> Int {
return str.reduce(0) { $0 + (dict[$1] ?? 0) }
}
f1("lighthouse") //52
This function reduces to a single digit but does not accepts strings.
func f2(_ n: Int) -> Int {
return (1 + ((n-1) % 9))
}
f2(52) //7
You just need to combine your both methods in the return statement:
return 1 + (str.reduce(0) {$0 + (dict[$1] ?? 0)} - 1) % 9
Here is a function that converts a string into a single digit Int.
Input: "9l231ffi1j"
func f1(_ str: String) -> Int {
return str.reduce(0) { $0 + ((Int(String($1)) ?? dict[$1]) ?? 0) }
}
func f3(_ input : String) -> Int {
return f2(f1(input))
}
f3("lighthouse") //7
f3("9l231ffi1j") //5
Thanks to Leo's answer in this question and Martin's answer in my other related [question] I was able to produce a standard version using Swift's for in loop without shorthand {$0 $1} and reduce().
let n = "lighthouse"
var nScore = 0
for i in n.indices {
let curChar = n[i]
let curVal = dict[curChar, default: 0]
nScore = 1 + (nScore + curVal - 1) % 9
}
nScore //7

I got an error swift on xcode7.

That error tells me that "Result value in ‘?’ :’ expression have mismatching types ’NSJONWritingOptions’ and ‘_'". Does anyone know how to fix this? I wrote these codes on xcode6.3.1 and converted to xcode7 just now. worked on xcode6 though....
public func toString(pretty:Bool=false)->String {
switch _value {
case is NSError: return "\(_value)"
case is NSNull: return "null"
case let o as NSNumber:
switch String.fromCString(o.objCType)! {
case "c", "C":
return o.boolValue.description
case "q", "l", "i", "s":
return o.longLongValue.description
case "Q", "L", "I", "S":
return o.unsignedLongLongValue.description
default:
switch o.doubleValue {
case 0.0/0.0: return "0.0/0.0" // NaN
case -1.0/0.0: return "-1.0/0.0" // -infinity
case +1.0/0.0: return "+1.0/0.0" // infinity
default:
return o.doubleValue.description
}
}
case let o as NSString:
return o.debugDescription
default:
let opts = pretty
//below is the code I got an error for
? NSJSONWritingOptions.PrettyPrinted : nil
if let data = (try? NSJSONSerialization.dataWithJSONObject(
_value, options:opts)) as NSData? {
if let result = NSString(
data:data, encoding:NSUTF8StringEncoding
) as? String {
return result
}
}
return "YOU ARE NOT SUPPOSED TO SEE THIS!"
}
}
options in NSJSONSerialization.dataWithJSONObject:options: should be an empty array if you don't want to specify any options. So your code should look like this:
let opts = pretty ? NSJSONWritingOptions.PrettyPrinted : []
Previously it expected nil, but there was change in the way iOS SDK is mapped in Swift.
I use this in my Swift 2.0 implementations-
let options = prettyPrinted ?
NSJSONWritingOptions.PrettyPrinted : NSJSONWritingOptions(rawValue: 0)

Swift protocol extensions with enums

So I have several enums representing various unit systems:
enum MassUnit:Double{
case Pound = 453.59237, Ounce = 28.349523125, Gram = 1.0, Kilogram = 1000.0;
}
enum VolumeUnit:Double{
case Teaspoon = 1, Tablespoon = 3, Cup = 48, Pint = 96, Quart = 192, Gallon = 768, Liter = 202.884136211, Milliliter = 0.202884136211
}
enum TimeUnit:Double{
case Second = 1, Minute = 60, Hour = 3600, Day = 86400, Week = 604800, Year = 31536000
}
What I would like to do is be able to convert from one unit to another, eg from year to seconds. To do this I've ensured that the raw values for my enums correspond to the converting multipliers, eg 1 Min = 60 seconds. Thus, given x amount of some unit, the conversion is simply
x * rawValue1 / rawValue2 // rawValue2 = rawValue of desired unit.
While that conversion is simple enough, I would love to be efficient and use a protocol:
protocol Convertable{
func convert(inputAmount inputAmount:Double, outputUnit:Self)->Double;
}
Then, I could extend the enum:
extension TimeUnit:Convertable{
func convert(inputAmount inputAmount: Double, outputUnit: TimeUnit) -> Double {
return inputAmount * self.rawValue / outputUnit.rawValue;
}
}
Then I could simply convert like this:
TimeUnit.Year.convert(inputAmount: 2.54, outputUnit: .Second)
// returns 80101440
This is great, however, depending on how many units I want to convert, there would be a lot of duplication of the same code.
So, what I would like to do is somehow use a protocol extension.
extension Convertable{
func convert(inputAmount inputAmount: Double, outputUnit: Self) -> Double {
return inputAmount * self.rawValue / outputUnit.rawValue;// Compile error...
}
}
This is where I get into trouble, the output unit is declared as self, which knows nothing about rawValue.
Any ideas?
Right when I was asking this question, the answer came to me: Have the Convertible protocol also require a rawValue variable:
protocol Convertable{
var rawValue:Double{get}
func convert(inputAmount inputAmount:Double, outputUnit:Self)->Double;
}
That way, referencing self.rawValue is known, and since enums already have a rawValue, no extra work is required:
enum MassUnit:Double, Convertable{
case Pound = 453.59237, Ounce = 28.349523125, Gram = 1.0, Kilogram = 1000.0;
}
enum VolumeUnit:Double, Convertable{
case Teaspoon = 1, Tablespoon = 3, Cup = 48, Pint = 96, Quart = 192, Gallon = 768, Liter = 202.884136211, Milliliter = 0.202884136211
}
enum TimeUnit:Double, Convertable{
case Second = 1, Minute = 60, Hour = 3600, Day = 86400, Week = 604800, Year = 31536000
}
And then using the converter:
TimeUnit.Year.convert(inputAmount: 2.54, outputUnit: .Second) // 80101440
MassUnit.Gram.convert(inputAmount: 20.0, outputUnit: .Ounce) //0.70547
VolumeUnit.Pint.convert(inputAmount: 0.2, outputUnit: .Tablespoon)// 6.4000
Even beter, in the odd case where a conversion doesn't work like this, I can override the convert function with my own implementation, such as in Temperature:
enum TemperatureUnit:Double, Convertable{
case Celsius, Fahrenheit
func convert(inputAmount inputAmount: Double, outputUnit: TemperatureUnit) -> Double {
if self == outputUnit {
return inputAmount;
} else if self == .Celsius {
return inputAmount * 9.0/5.0 + 32.0
} else {
return (inputAmount - 32.0) * 5.0 / 9.0;
}
}
}
TemperatureUnit.Celsius.convert(inputAmount: 3, outputUnit: .Fahrenheit) // 37.4
TemperatureUnit.Fahrenheit.convert(inputAmount: 0, outputUnit: .Celsius) // -17.7778
Beautiful!