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?
Convert Swift String to DoubleUse Double , Float , CGFloat to convert floating-point values (real numbers). let lessPrecisePI = Float("3.14") let morePrecisePI = Double("3.1415926536") let width = CGFloat(Double("200.0")!)
To convert a string to a double, we can use the built-in Double() initializer syntax in Swift. The Double() initializer takes the string as an input and returns the double instance. If the string contains invalid characters or invalid format it returns nil.
We can convert numbers to strings through using the str() method.
To convert an Int value to a String value in Swift, use String(). String() accepts integer as argument and returns a String value created using the given integer value.
Xcode 11 • Swift 5.1 or later
To convert from String
or any Numeric
type to Data
:
extension StringProtocol {
var data: Data { .init(utf8) }
}
extension Numeric {
var data: Data {
var source = self
// This will return 1 byte for 8-bit, 2 bytes for 16-bit, 4 bytes for 32-bit and 8 bytes for 64-bit binary integers. For floating point types it will return 4 bytes for single-precision, 8 bytes for double-precision and 16 bytes for extended precision.
return .init(bytes: &source, count: MemoryLayout<Self>.size)
}
}
To convert from Data
(bytes) back to String
extension DataProtocol {
var string: String? { String(bytes: self, encoding: .utf8) }
}
To convert from Data
back to generic Numeric
value
extension Numeric {
init<D: DataProtocol>(_ data: D) {
var value: Self = .zero
let size = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(size == MemoryLayout.size(ofValue: value))
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 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
let stringBytes = "Hello World !!!".data.prefix(4) // 4 bytes
let backToString = stringBytes.string // "Hell"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With