Chris Lowrie
Chris Lowrie

Reputation: 131

How can I convert data into types like Doubles, Ints and Strings in Swift?

I'm working on building a custom file opener in iOS Swift for shapefiles (a GIS format, not particularly relevant to this question). These files have a header which is 100 bytes long. I'm able to read this into 4-byte arrays, which store information I want. I can convert these arrays into the Swift types Data and NSData, and have a few other options for transforming them (like Base64EncodedString). But I'm having trouble converting these raw arrays or the Data or any of the formats into useful attributes like Double, Int, and String.

import Foundation
    struct ShapeReader {
        var shapeFile = FileHandle(forReadingAtPath: "/Users/christopherjlowrie/Documents/Shapes/SF_Neighborhoods/Planning_Zones.shp")
        var fileHeader: String{
            let header = shapeFile?.readData(ofLength: 100)
            let headerStream = InputStream(data: header!)
            headerStream.open()
            var buffer = [UInt8](repeating: 0, count: 4)
            while (headerStream.hasBytesAvailable){
                headerStream.read(&buffer, maxLength: buffer.count)
                print(buffer)
                let x = Data(buffer)
                print(x)
        }
        return "A"
    }
}

This currently only returns A because for testing reasons I am having it return a string

How can I open files, and read their raw bytes into types (Doubles, Ints, Strings) in Swift?

Upvotes: 12

Views: 9357

Answers (1)

Leo Dabus
Leo Dabus

Reputation: 236380

Xcode 11 • Swift 5.1 or later

To convert from String to DataProtocol (Data or Bytes):

extension DataProtocol where Self: RangeReplaceableCollection {
    init<S: StringProtocol>(_ string: S) {
        self.init(string.utf8)
    }
}

extension StringProtocol {
    var data: Data { .init(self) }
    var bytes: [UInt8] { .init(self) }
}

To convert from DataProtocol back to String

extension DataProtocol {
    var string: String? { String(bytes: self, encoding: .utf8) }
}

let stringData = "abc".data               // 3 bytes
let stringBytes = "abc".bytes             // [97, 98, 99]
let stringFromData = stringData.string    // "abc"
let stringFromBytes = stringBytes.string  // "abc"


To convert from any Numeric type to DataProtocol:

extension DataProtocol where Self: RangeReplaceableCollection {
    init<N: Numeric>(_ numeric: N) {
        self = withUnsafeBytes(of: numeric) { .init($0) }
    }
}

extension Numeric {
    var data: Data { .init(self) }
    var bytes: [UInt8] { .init(self) }
}

To convert from DataProtocol back to any Numeric type:

extension Numeric {
    init<D: DataProtocol>(_ data: D) {
        var value: Self = .zero
        let size = withUnsafeMutableBytes(of: &value, data.copyBytes)
        self = value
    }
}

extension DataProtocol {
    func value<N: Numeric>() -> N { .init(self) }
}

let value = 12.34                      // implicit Double 12.34
let data = value.data                  // double data - 8 bytes
let bytes = value.bytes                // [113, 61, 10, 215, 163, 16, 69, 64]
let double = Double(data)              // implicit Double 12.34
let double1: Double = .init(data)      // explicit Double 12.34
let double2: Double = data.value()     // explicit Double 12.34
let double3 = data.value() as Double   // casting to Double 12.34

Now we can easily add a property for each Numeric type:

extension DataProtocol {
    var integer: Int { value() }
    var int32: Int32 { value() }
    var float: Float { value() }
    var cgFloat: CGFloat { value() }
    var float80: Float80 { value() }
    var double: Double { value() }
    var decimal: Decimal { value() }
}

Playground testing

let intData = 1_234_567_890_123_456_789.data    // 8 bytes (64 bit Integer)
let dataToInt: Int = intData.integer                 // 1234567890123456789

let intMinData = Int.min.data                   // 8 bytes (64 bit Integer)
let backToIntMin = intMinData.integer           // -9223372036854775808

let intMaxData = Int.max.data                   // 8 bytes (64 bit Integer)
let backToIntMax = intMaxData.integer           // 9223372036854775807

let myInt32Data = Int32(1_234_567_890).data     // 4 bytes (32 bit Integer)
let backToInt32 = myInt32Data.int32             // 1234567890

let int32MinData = Int32.min.data               // 4 bytes (32 bit Integer)
let backToInt32Min = int32MinData.int32         // -2147483648

let int32MaxData = Int32.max.data               // 4 bytes (32 bit Integer)
let backToInt32Max = int32MaxData.int32         // 2147483647

let myFloatData = Float.pi.data                 // 4 bytes (32 bit single=precison FloatingPoint)
let backToFloat = myFloatData.float             // 3.141593
backToFloat == .pi      // true

let myCGFloatData = CGFloat.pi.data                 // 4 bytes (32 bit single=precison FloatingPoint)
let backToCGFloat = myCGFloatData.cgFloat             // 3.141593
backToCGFloat == .pi      // true

let myDoubleData = Double.pi.data               // 8 bytes (64 bit double-precision FloatingPoint)
let backToDouble = myDoubleData.double          // 3.141592653589793
backToDouble == .pi     // true

let myFloat80Data = Float80.pi.data             // 16 bytes (128 bit extended-precision FloatingPoint)
let backToFloat80 = myFloat80Data.float80       // 3.141592653589793116
backToFloat80 == .pi    // true

let decimalData = Decimal.pi.data             // 20 bytes Decimal type
let backToDecimal = decimalData.decimal       // 3.14159265358979323846264338327950288419
backToDecimal == .pi    // true

Upvotes: 24

Related Questions