I am trying to decode a base64 string to an image in Swift using the following code:
let decodedData=NSData(base64EncodedString: encodedImageData, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
Unfortunately, the variable decodedData turns out to have a value of nil
Debugging through the code, I verified that the variable encodedImageData is not nil and is the correct encoded image data(verified by using an online base64 to image converter). What could possibly be the reason behind my issue?
This method requires padding with “=“, the length of the string must be multiple of 4.
In some implementations of base64 the padding character is not needed for decoding, since the number of missing bytes can be calculated. But in Fundation's implementation it is mandatory.
Updated:
As noted on the comments, it's a good idea to check first if the string lenght is already a multiple of 4. if encoded64 has your base64 string and it's not a constant, you can do something like this:
Swift 2
let remainder = encoded64.characters.count % 4
if remainder > 0 {
encoded64 = encoded64.stringByPaddingToLength(encoded64.characters.count + 4 - remainder,
withPad: "=",
startingAt: 0)
}
Swift 3
let remainder = encoded64.characters.count % 4
if remainder > 0 {
encoded64 = encoded64.padding(toLength: encoded64.characters.count + 4 - remainder,
withPad: "=",
startingAt: 0)
}
Swift 4
let remainder = encoded64.count % 4
if remainder > 0 {
encoded64 = encoded64.padding(toLength: encoded64.count + 4 - remainder,
withPad: "=",
startingAt: 0)
}
Updated one line version:
Or you can use this one line version that returns the same string when its length is already a multiple of 4:
encoded64.padding(toLength: ((encoded64.count+3)/4)*4,
withPad: "=",
startingAt: 0)
When the number of characters is divisible by 4, you need to avoid padding.
private func base64PaddingWithEqual(encoded64: String) -> String {
let remainder = encoded64.characters.count % 4
if remainder == 0 {
return encoded64
} else {
// padding with equal
let newLength = encoded64.characters.count + (4 - remainder)
return encoded64.stringByPaddingToLength(newLength, withString: "=", startingAtIndex: 0)
}
}
(Swift 3)
I been in this situation, Trying to get the data using base64 encoded string returns nil with me when I used this line
let imageData = Data(base64Encoded: strBase64, options: .ignoreUnknownCharacters)
Tried padding the string and didn't work out too
This is what worked with me
func imageForBase64String(_ strBase64: String) -> UIImage? {
do{
let imageData = try Data(contentsOf: URL(string: strBase64)!)
let image = UIImage(data: imageData)
return image!
}
catch{
return nil
}
}
Check the content of your decodedData variable and look for this prefix "data:image/png;base64", I had this issue and noticed that my String base64 had a prefix like this, so I used this approached and it worked
extension String {
func getImageFromBase64() -> UIImage? {
guard let url = URL(string: self) else {
return nil
}
do {
let data = try Data(contentsOf: url)
return UIImage(data: data)
} catch {
return nil
}
}
}
This helped me:
extension String {
func fromBase64() -> String? {
guard let data = Data(base64Encoded: self.replacingOccurrences(of: "_", with: "="), options: Data.Base64DecodingOptions(rawValue: 0)) else {
return nil
}
return String(data: data, encoding: .utf8)
}
}
Usage:
print(base64EncodedString.fromBase64())
Another one-line version:
let length = encoded64.characters.count
encoded64 = encoded64.padding(toLength: length + (4 - length % 4) % 4, withPad: "=", startingAt: 0)
It's make problem with special character, but an interesting point is if we use NSData and NSString then it's working fine.
static func decodeBase64(input: String)->String{
let base64Decoded = NSData(base64Encoded: input, options: NSData.Base64DecodingOptions(rawValue: 0))
.map({ NSString(data: $0 as Data, encoding: String.Encoding.utf8.rawValue) })
return base64Decoded!! as String
}
You can use this extension to make sure that the string has the correct length fro decoding with Foundation (divisible by 4):
extension String {
var paddedForBase64Decoding: String {
appending(String(repeating: "=", count: (4 - count % 4) % 4))
}
}
Usage:
Data(base64Encoded: base64String.paddedForBase64Decoding)
Related
I'm used to do this in JavaScript:
var domains = "abcde".substring(0, "abcde".indexOf("cd")) // Returns "ab"
Swift doesn't have this function, how to do something similar?
edit/update:
Xcode 11.4 • Swift 5.2 or later
import Foundation
extension StringProtocol {
func index<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
range(of: string, options: options)?.lowerBound
}
func endIndex<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
range(of: string, options: options)?.upperBound
}
func indices<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Index] {
ranges(of: string, options: options).map(\.lowerBound)
}
func ranges<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Range<Index>] {
var result: [Range<Index>] = []
var startIndex = self.startIndex
while startIndex < endIndex,
let range = self[startIndex...]
.range(of: string, options: options) {
result.append(range)
startIndex = range.lowerBound < range.upperBound ? range.upperBound :
index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
}
return result
}
}
usage:
let str = "abcde"
if let index = str.index(of: "cd") {
let substring = str[..<index] // ab
let string = String(substring)
print(string) // "ab\n"
}
let str = "Hello, playground, playground, playground"
str.index(of: "play") // 7
str.endIndex(of: "play") // 11
str.indices(of: "play") // [7, 19, 31]
str.ranges(of: "play") // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}]
case insensitive sample
let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] } //
print(matches) // ["play", "play", "play"]
regular expression sample
let query = "play"
let escapedQuery = NSRegularExpression.escapedPattern(for: query)
let pattern = "\\b\(escapedQuery)\\w+" // matches any word that starts with "play" prefix
let ranges = str.ranges(of: pattern, options: .regularExpression)
let matches = ranges.map { str[$0] }
print(matches) // ["playground", "playground", "playground"]
Using String[Range<String.Index>] subscript you can get the sub string. You need starting index and last index to create the range and you can do it as below
let str = "abcde"
if let range = str.range(of: "cd") {
let substring = str[..<range.lowerBound] // or str[str.startIndex..<range.lowerBound]
print(substring) // Prints ab
}
else {
print("String not present")
}
If you don't define the start index this operator ..< , it take the starting index. You can also use str[str.startIndex..<range.lowerBound] instead of str[..<range.lowerBound]
Swift 5
Find index of substring
let str = "abcdecd"
if let range: Range<String.Index> = str.range(of: "cd") {
let index: Int = str.distance(from: str.startIndex, to: range.lowerBound)
print("index: ", index) //index: 2
}
else {
print("substring not found")
}
Find index of Character
let str = "abcdecd"
if let firstIndex = str.firstIndex(of: "c") {
let index = str.distance(from: str.startIndex, to: firstIndex)
print("index: ", index) //index: 2
}
else {
print("symbol not found")
}
In Swift 4 :
Getting Index of a character in a string :
let str = "abcdefghabcd"
if let index = str.index(of: "b") {
print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1))
}
Creating SubString (prefix and suffix) from String using Swift 4:
let str : String = "ilike"
for i in 0...str.count {
let index = str.index(str.startIndex, offsetBy: i) // String.Index
let prefix = str[..<index] // String.SubSequence
let suffix = str[index...] // String.SubSequence
print("prefix \(prefix), suffix : \(suffix)")
}
Output
prefix , suffix : ilike
prefix i, suffix : like
prefix il, suffix : ike
prefix ili, suffix : ke
prefix ilik, suffix : e
prefix ilike, suffix :
If you want to generate a substring between 2 indices , use :
let substring1 = string[startIndex...endIndex] // including endIndex
let subString2 = string[startIndex..<endIndex] // excluding endIndex
Doing this in Swift is possible but it takes more lines, here is a function indexOf() doing what is expected:
func indexOf(source: String, substring: String) -> Int? {
let maxIndex = source.characters.count - substring.characters.count
for index in 0...maxIndex {
let rangeSubstring = source.startIndex.advancedBy(index)..<source.startIndex.advancedBy(index + substring.characters.count)
if source.substringWithRange(rangeSubstring) == substring {
return index
}
}
return nil
}
var str = "abcde"
if let indexOfCD = indexOf(str, substring: "cd") {
let distance = str.startIndex.advancedBy(indexOfCD)
print(str.substringToIndex(distance)) // Returns "ab"
}
This function is not optimized but it does the job for short strings.
There are three closely connected issues here:
All the substring-finding methods are over in the Cocoa NSString world (Foundation)
Foundation NSRange has a mismatch with Swift Range; the former uses start and length, the latter uses endpoints
In general, Swift characters are indexed using String.Index, not Int, but Foundation characters are indexed using Int, and there is no simple direct translation between them (because Foundation and Swift have different ideas of what constitutes a character)
Given all that, let's think about how to write:
func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
// ?
}
The substring s2 must be sought in s using a String Foundation method. The resulting range comes back to us, not as an NSRange (even though this is a Foundation method), but as a Range of String.Index (wrapped in an Optional, in case we didn't find the substring at all). However, the other number, from, is an Int. Thus we cannot form any kind of range involving them both.
But we don't have to! All we have to do is slice off the end of our original string using a method that takes a String.Index, and slice off the start of our original string using a method that takes an Int. Fortunately, such methods exist! Like this:
func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
guard let r = s.range(of:s2) else {return nil}
var s = s.prefix(upTo:r.lowerBound)
s = s.dropFirst(from)
return s
}
Or, if you prefer to be able to apply this method directly to a string, like this...
let output = "abcde".substring(from:0, toSubstring:"cd")
...then make it an extension on String:
extension String {
func substring(from:Int, toSubstring s2 : String) -> Substring? {
guard let r = self.range(of:s2) else {return nil}
var s = self.prefix(upTo:r.lowerBound)
s = s.dropFirst(from)
return s
}
}
Swift 5
let alphabet = "abcdefghijklmnopqrstuvwxyz"
var index: Int = 0
if let range: Range<String.Index> = alphabet.range(of: "c") {
index = alphabet.distance(from: alphabet.startIndex, to: range.lowerBound)
print("index: ", index) //index: 2
}
Swift 5
extension String {
enum SearchDirection {
case first, last
}
func characterIndex(of character: Character, direction: String.SearchDirection) -> Int? {
let fn = direction == .first ? firstIndex : lastIndex
if let stringIndex: String.Index = fn(character) {
let index: Int = distance(from: startIndex, to: stringIndex)
return index
} else {
return nil
}
}
}
tests:
func testFirstIndex() {
let res = ".".characterIndex(of: ".", direction: .first)
XCTAssert(res == 0)
}
func testFirstIndex1() {
let res = "12345678900.".characterIndex(of: "0", direction: .first)
XCTAssert(res == 9)
}
func testFirstIndex2() {
let res = ".".characterIndex(of: ".", direction: .last)
XCTAssert(res == 0)
}
func testFirstIndex3() {
let res = "12345678900.".characterIndex(of: "0", direction: .last)
XCTAssert(res == 10)
}
In the Swift version 3, String doesn't have functions like -
str.index(of: String)
If the index is required for a substring, one of the ways to is to get the range. We have the following functions in the string which returns range -
str.range(of: <String>)
str.rangeOfCharacter(from: <CharacterSet>)
str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>)
For example to find the indexes of first occurrence of play in str
var str = "play play play"
var range = str.range(of: "play")
range?.lowerBound //Result : 0
range?.upperBound //Result : 4
Note : range is an optional. If it is not able to find the String it will make it nil. For example
var str = "play play play"
var range = str.range(of: "zoo") //Result : nil
range?.lowerBound //Result : nil
range?.upperBound //Result : nil
Leo Dabus's answer is great. Here is my answer based on his answer using compactMap to avoid Index out of range error.
Swift 5.1
extension StringProtocol {
func ranges(of targetString: Self, options: String.CompareOptions = [], locale: Locale? = nil) -> [Range<String.Index>] {
let result: [Range<String.Index>] = self.indices.compactMap { startIndex in
let targetStringEndIndex = index(startIndex, offsetBy: targetString.count, limitedBy: endIndex) ?? endIndex
return range(of: targetString, options: options, range: startIndex..<targetStringEndIndex, locale: locale)
}
return result
}
}
// Usage
let str = "Hello, playground, playground, playground"
let ranges = str.ranges(of: "play")
ranges.forEach {
print("[\($0.lowerBound.utf16Offset(in: str)), \($0.upperBound.utf16Offset(in: str))]")
}
// result - [7, 11], [19, 23], [31, 35]
Have you considered using NSRange?
if let range = mainString.range(of: mySubString) {
//...
}
In Swift 4 we could use
var md5: String? {
guard let data = self.data(using: .utf8) else { return nil }
let hash = data.withUnsafeBytes { (bytes: UnsafePointer<Data>) -> [UInt8] in
var hash: [UInt8] = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5(bytes, CC_LONG(data.count), &hash)
return hash
}
return hash.map { String(format: "%02x", $0) }.joined()
}
But in Swift 5 withUnsafeBytes uses UnsafeRawBufferPointer instead of UnsafePointer. How to change md5 function?
Swift 5 version: Use UnsafeRawBufferPointer as type of the closure argument, and bytes.baseAddress to pass address to the Common Crypto function:
import Foundation
import CommonCrypto
extension String {
var md5: String {
let data = Data(self.utf8)
let hash = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in
var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5(bytes.baseAddress, CC_LONG(data.count), &hash)
return hash
}
return hash.map { String(format: "%02x", $0) }.joined()
}
}
(Note that the conversion of a string to UTF-8 data cannot fail, there is no need to return an optional.)
CC_MD5 has been deprecated with the iOS 13. Instead, you can use CC_SHA256.
In iOS 13 and above there is a framework CryptoKit which is a wrapper around CommonCrypto framework and around the MD5 hash function.
import CryptoKit
let d = "Hello"
let r = Insecure.MD5.hash(data: d.data(using: .utf8)!)
print(r)
/*Output: MD5 digest: 8b1a9953c4611296a827abf8c47804d7*/
In iOS 13 and above there is a framework CryptoKit. Try using this:
extension Data {
var md5: String {
Insecure.MD5
.hash(data: self)
.map {String(format: "%02x", $0)}
.joined()
}
}
Eskimo's solution
Below is a variant based on a solution proposed by Eskimo from Apple in Swift Forum post withUnsafeBytes Data API confusion:
extension String {
func md5() -> String {
let data = Data(utf8)
var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
data.withUnsafeBytes { buffer in
_ = CC_MD5(buffer.baseAddress, CC_LONG(buffer.count), &hash)
}
return hash.map { String(format: "%02hhx", $0) }.joined()
}
}
Note it is effectively the same as Martin R's solution, but a line shorter (no return hash).
Solution using NSData
This is an even shorter solution using bridging to NSData.
extension String {
func md5() -> String {
let data = Data(utf8) as NSData
var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5(data.bytes, CC_LONG(data.length), &hash)
return hash.map { String(format: "%02hhx", $0) }.joined()
}
}
CC_MD5 gives back
'CC_MD5' was deprecated in iOS 13.0: This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).
so to have a flexible solution:
//OLD
import CommonCrypto
//new:
import CryptoKit
extension String {
var md5: String {
if #available(iOS 13.0, *) {
guard let d = self.data(using: .utf8) else { return ""}
let digest = Insecure.MD5.hash(data: d)
let h = digest.reduce("") { (res: String, element) in
let hex = String(format: "%02x", element)
//print(ch, hex)
let t = res + hex
return t
}
return h
} else {
// Fall back to pre iOS13
let length = Int(CC_MD5_DIGEST_LENGTH)
var digest = [UInt8](repeating: 0, count: length)
if let d = self.data(using: .utf8) {
_ = d.withUnsafeBytes { body -> String in
CC_MD5(body.baseAddress, CC_LONG(d.count), &digest)
return ""
}
}
let result = (0 ..< length).reduce("") {
$0 + String(format: "%02x", digest[$1])
}
return result
}// end of fall back
}
}
to test:
func MD5Test() -> Bool{
let HASHED = "5D41402ABC4B2A76B9719D911017C592"
let s = "hello"
let md5_1 = s.md5
if md5_1.uppercased() != HASHED{
return false
}
return true
}
I've attempted to research ways to take a given word and calculate the number of possible anagrams a user can make from that word eg an 8 letter word such as snowbanks has 5 eight letter possibilities, 25 seven letter possibilities, etc (those are made up numbers). My initial plan would be to iterate over a dictionary list and check each of the words to see if it is an anagram of the word in question as I've seen suggested in other places.
Rearrange Letters from Array and check if arrangement is in array
seemed very promising, except that it is in objective C and when I tried to convert it to Swift using Swiftify I couldn't get it to work as shown below:
func findAnagrams() -> Set<AnyHashable>? {
let nineCharacters = [unichar](repeating: 0, count: 8)
let anagramKey = self.anagramKey()
// make sure this word is not too long/short.
if anagramKey == nil {
return nil
}
(anagramKey as NSString?)?.getCharacters(nineCharacters, range: NSRange)
let middleCharPos = Int((anagramKey as NSString?)?.range(of: (self as NSString).substring(with: NSRange)).location ?? 0)
var anagrams = Set<AnyHashable>()
// 0x1ff means first 9 bits set: one for each character
for i in 0...0x1ff {
// skip permutations that do not contain the middle letter
if (i & (1 << middleCharPos)) == 0 {
continue
}
var length: Int = 0
var permutation = [unichar](repeating: 0, count: 9)
for bit in 0...9 {
if true {
permutation[length] = nineCharacters[bit]
length += 1
}
}
if length < 4 {
continue
}
let permutationString = String(permutation)
let matchingAnagrams = String.anagramMap()[permutationString] as? [Any]
for word: String? in matchingAnagrams {
anagrams.insert(word)
}
}
return anagrams
}
class func anagramMap() -> [AnyHashable: Any]? {
var anagramMap: [AnyHashable: Any]
if anagramMap != nil {
return anagramMap
}
// this file is present on Mac OS and other unix variants
let allWords = try? String(contentsOfFile: "/usr/share/dict/words", encoding: .utf8)
var map = [AnyHashable: Any]()
autoreleasepool {
allWords?.enumerateLines(invoking: {(_ word: String?, _ stop: UnsafeMutablePointer<ObjCBool>?) -> Void in
let key = word?.anagramKey()
if key == nil {
return
}
var keyWords = map[key] as? [AnyHashable]
if keyWords == nil {
keyWords = [AnyHashable]()
map[key] = keyWords
}
if let aWord = word {
keyWords?.append(aWord)
}
})
}
anagramMap = map
return anagramMap
}
func anagramKey() -> String? {
let lowercaseWord = word.lowercased()
// make sure to take the length *after* lowercase. it might change!
let length: Int = lowercaseWord.count
// in this case we're only interested in anagrams 4 - 9 characters long
if length < 3 || length > 9 {
return nil
}
let sortedWord = [unichar](repeating: 0, count: length)
(lowercaseWord as NSString).getCharacters(sortedWord, range: NSRange)
qsort_b(sortedWord, length, MemoryLayout<unichar>.size, {(_ aPtr: UnsafeRawPointer?, _ bPtr: UnsafeRawPointer?) -> Int in
let a = Int(unichar(aPtr))
let b = Int(unichar(bPtr))
return b - a
})
return String(describing: sortedWord)
}
func isReal(word: String) -> Bool {
let checker = UITextChecker()
let range = NSMakeRange(0, word.utf16.count)
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
return misspelledRange.location == NSNotFound
}
}
I've also tried the following in an attempt to just produce a list of words that I could iterate over to check for anagrams (I have working code that checks guesses vs the main word to check for anagrams) but I wasn't able to get them to work, possibly because they require a file to be copied to the app, since I was under the impression that the phone has a dictionary preloaded that I could use for words (although I may be mistaken):
var allTheWords = try? String(contentsOfFile: "/usr/share/dict/words", encoding: .utf8)
for line: String? in allTheWords?.components(separatedBy: "\n") ?? [String?]() {
print("\(line ?? "")")
print("Double Fail \(allTheWords)")
}
and
if let wordsFilePath = Bundle.main.path(forResource: "dict", ofType: nil) {
do {
let wordsString = try String(contentsOfFile: wordsFilePath)
let wordLines = wordsString.components(separatedBy: NSCharacterSet.newlines)
let randomLine = wordLines[Int(arc4random_uniform(UInt32(wordLines.count)))]
print(randomLine)
} catch { // contentsOfFile throws an error
print("Error: \(error)")
}
}
}
I looked at UIReferenceLibraryViewController as well in an attempt to use it to produce a list of words instead of defining a selected word, but the following isn't a valid option.
let words = UIReferenceLibraryViewController.enumerated
Any assistance would be greatly appreciated!
My code snippet is:
unwanted = " £€₹jetztabfromnow"
let favouritesPriceLabel = priceDropsCollectionView.cells.element(boundBy: UInt(index)).staticTexts[IPCUIAHighlightsPriceDropsCollectionViewCellPriceLabel].label
let favouritesPriceLabelTrimmed = favouritesPriceLabel.components(separatedBy: "jetzt").flatMap { String($0.trimmingCharacters(in: .whitespaces)) }.last
favouritesHighlightsDictionary[favouritesTitleLabel] = favouritesPriceLabelTrimmed
My problem is, this didn't work:
let favouritesPriceLabelTrimmed = favouritesPriceLabel.components(separatedBy: unwanted).flatMap { String($0.trimmingCharacters(in: .whitespaces)) }.last
I have a price like "from 3,95 €" - I want to cut all currencies "£€₹" and words like "from" or "ab"
Do you have a solution for me, what I can use here?
Rather than mess around with trying to replace or remove the right characters or using regular expressions, I'd go with Foundation's built-in linguistic tagging support. It will do a lexical analysis of the string and return tokens of various types. Use it on this kind of string and it should reliably find any numbers in the string.
Something like:
var str = "from 3,95 €"
let range = Range(uncheckedBounds: (lower: str.startIndex, upper: str.endIndex))
var tokenRanges = [Range<String.Index>]()
let scheme = NSLinguisticTagSchemeLexicalClass
let option = NSLinguisticTagger.Options()
let tags = str.linguisticTags(in: range, scheme: scheme, options: option, orthography: nil, tokenRanges: &tokenRanges)
let tokens = tokenRanges.map { str.substring(with:$0) }
if let numberTagIndex = tags.index(where: { $0 == "Number" }) {
let number = tokens[numberTagIndex]
print("Found number: \(number)")
}
In this example the code prints "3,95". If you change str to "from £28.50", it prints "28.50".
One way is to place the unwanted strings into an array, and use String's replacingOccurrences(of:with:) method.
let stringToScan = "£28.50"
let toBeRemoved = ["£", "€", "₹", "ab", "from"]
var result = stringToScan
toBeRemoved.forEach { result = result.replacingOccurrences(of: $0, with: "") }
print(result)
...yields "28.50".
If you just want to extract the numeric value use regular expression, it considers comma or dot decimal separators.
let string = "from 3,95 €"
let pattern = "\\d+[.,]\\d+"
do {
let regex = try NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) {
let range = match.range
let start = string.index(string.startIndex, offsetBy: range.location)
let end = string.index(start, offsetBy: range.length)
print(string.substring(with: start..<end)) // 3,95
} else {
print("Not found")
}
} catch {
print("Regex Error:", error)
}
I asked if you had a fixed locale for this string, because then you can use the locale to determine what the decimal separator is: For example, try this in a storyboard.
let string = "some initial text 3,95 €" // define the string to scan
// Add a convenience extension to Scanner so you don't have to deal with pointers directly.
extension Scanner {
func scanDouble() -> Double? {
var value = Double(0)
guard scanDouble(&value) else { return nil }
return value
}
// Convenience method to advance the location of the scanner up to the first digit. Returning the scanner itself or nil, which allows for optional chaining
func scanUpToNumber() -> Scanner? {
var value: NSString?
guard scanUpToCharacters(from: CharacterSet.decimalDigits, into: &value) else { return nil }
return self
}
}
let scanner = Scanner(string: string)
scanner.locale = Locale(identifier: "fr_FR")
let double = scanner.scanUpToNumber()?.scanDouble() // -> double = 3.95 (note the type is Double?)
Scanners are a lot easier to use than NSRegularExpressions in these cases.
You can filter by special character by removing alphanumerics.
extension String {
func removeCharacters(from forbiddenChars: CharacterSet) -> String {
let passed = self.unicodeScalars.filter { !forbiddenChars.contains($0) }
return String(String.UnicodeScalarView(passed))
}
}
let str = "£€₹jetztabfromnow12"
let t1 = str.removeCharacters(from: CharacterSet.alphanumerics)
print(t1) // will print: £€₹
let t2 = str.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(t2) // will print: 12
Updated 1:
var str = "£3,95SS"
str = str.replacingOccurrences(of: ",", with: "")
let digit = str.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(digit) // will print: 395
let currency = str.removeCharacters(from: CharacterSet.alphanumerics)
print(currency) // will print: £
let amount = currency + digit
print(amount) // will print: £3,95
Update 2:
let string = "£3,95SS"
let pattern = "-?\\d+(,\\d+)*?\\.?\\d+?"
do {
let regex = try NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) {
let range = match.range
let start = string.index(string.startIndex, offsetBy: range.location)
let end = string.index(start, offsetBy: range.length)
let digit = string.substring(with: start..<end)
print(digit) //3,95
let symbol = string.removeCharacters(from: CharacterSet.symbols.inverted)
print(symbol) // £
print(symbol + digit) //£3,95
} else {
print("Not found")
}
} catch {
print("Regex Error:", error)
}
I got the code to convert String to HEX-String in Objective-C:
- (NSString *) CreateDataWithHexString:(NSString*)inputString {
NSUInteger inLength = [inputString length];
unichar *inCharacters = alloca(sizeof(unichar) * inLength);
[inputString getCharacters:inCharacters range:NSMakeRange(0, inLength)];
UInt8 *outBytes = malloc(sizeof(UInt8) * ((inLength / 2) + 1));
NSInteger i, o = 0;
UInt8 outByte = 0;
for (i = 0; i < inLength; i++) {
UInt8 c = inCharacters[i];
SInt8 value = -1;
if (c >= '0' && c <= '9') value = (c - '0');
else if (c >= 'A' && c <= 'F') value = 10 + (c - 'A');
else if (c >= 'a' && c <= 'f') value = 10 + (c - 'a');
if (value >= 0) {
if (i % 2 == 1) {
outBytes[o++] = (outByte << 4) | value;
outByte = 0;
} else {
outByte = value;
}
} else {
if (o != 0) break;
}
}
NSData *a = [[NSData alloc] initWithBytesNoCopy:outBytes length:o freeWhenDone:YES];
NSString* newStr = [NSString stringWithUTF8String:[a bytes]];
return newStr;
}
I want the same in Swift. Can anybody translate this code in Swift, or is there any easy way to do this in Swift?
This is my hex string to Data routine:
extension String {
/// Create `Data` from hexadecimal string representation
///
/// This creates a `Data` object from hex string. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed.
///
/// - returns: Data represented by this hexadecimal string.
var hexadecimal: Data? {
var data = Data(capacity: count / 2)
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
let byteString = (self as NSString).substring(with: match!.range)
let num = UInt8(byteString, radix: 16)!
data.append(num)
}
guard data.count > 0 else { return nil }
return data
}
}
And for the sake of completeness, this is my Data to hex string routine:
extension Data {
/// Hexadecimal string representation of `Data` object.
var hexadecimal: String {
return map { String(format: "%02x", $0) }
.joined()
}
}
Note, as shown in the above, I generally only convert between hexadecimal representations and NSData instances (because if the information could have been represented as a string you probably wouldn't have created a hexadecimal representation in the first place). But your original question wanted to convert between hexadecimal representations and String objects, and that might look like so:
extension String {
/// Create `String` representation of `Data` created from hexadecimal string representation
///
/// This takes a hexadecimal representation and creates a String object from that. Note, if the string has any spaces, those are removed. Also if the string started with a `<` or ended with a `>`, those are removed, too.
///
/// For example,
///
/// String(hexadecimal: "<666f6f>")
///
/// is
///
/// Optional("foo")
///
/// - returns: `String` represented by this hexadecimal string.
init?(hexadecimal string: String, encoding: String.Encoding = .utf8) {
guard let data = string.hexadecimal() else {
return nil
}
self.init(data: data, encoding: encoding)
}
/// Create hexadecimal string representation of `String` object.
///
/// For example,
///
/// "foo".hexadecimalString()
///
/// is
///
/// Optional("666f6f")
///
/// - parameter encoding: The `String.Encoding` that indicates how the string should be converted to `Data` before performing the hexadecimal conversion.
///
/// - returns: `String` representation of this String object.
func hexadecimalString(encoding: String.Encoding = .utf8) -> String? {
return data(using: encoding)?
.hexadecimal
}
}
You could then use the above like so:
let hexString = "68656c6c 6f2c2077 6f726c64"
print(String(hexadecimal: hexString))
Or,
let originalString = "hello, world"
print(originalString.hexadecimalString())
For permutations of the above for earlier Swift versions, see the revision history of this question.
convert hex string to data and string:
Swift1
func dataWithHexString(hex: String) -> NSData {
var hex = hex
let data = NSMutableData()
while(countElements(hex) > 0) {
var c: String = hex.substringToIndex(advance(hex.startIndex, 2))
hex = hex.substringFromIndex(advance(hex.startIndex, 2))
var ch: UInt32 = 0
NSScanner(string: c).scanHexInt(&ch)
data.appendBytes(&ch, length: 1)
}
return data
}
use:
let data = dataWithHexString("68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
if let string = NSString(data: data, encoding: 1) {
print(string) // hello, world
}
Swift2
func dataWithHexString(hex: String) -> NSData {
var hex = hex
let data = NSMutableData()
while(hex.characters.count > 0) {
let c: String = hex.substringToIndex(hex.startIndex.advancedBy(2))
hex = hex.substringFromIndex(hex.startIndex.advancedBy(2))
var ch: UInt32 = 0
NSScanner(string: c).scanHexInt(&ch)
data.appendBytes(&ch, length: 1)
}
return data
}
use:
let data = dataWithHexString("68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
if let string = String(data: data, encoding: NSUTF8StringEncoding) {
print(string) //"hello, world"
}
Swift3
func dataWithHexString(hex: String) -> Data {
var hex = hex
var data = Data()
while(hex.characters.count > 0) {
let c: String = hex.substring(to: hex.index(hex.startIndex, offsetBy: 2))
hex = hex.substring(from: hex.index(hex.startIndex, offsetBy: 2))
var ch: UInt32 = 0
Scanner(string: c).scanHexInt32(&ch)
var char = UInt8(ch)
data.append(&char, count: 1)
}
return data
}
use:
let data = dataWithHexString(hex: "68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
let string = String(data: data, encoding: .utf8) // "hello, world"
Swift4
func dataWithHexString(hex: String) -> Data {
var hex = hex
var data = Data()
while(hex.count > 0) {
let subIndex = hex.index(hex.startIndex, offsetBy: 2)
let c = String(hex[..<subIndex])
hex = String(hex[subIndex...])
var ch: UInt32 = 0
Scanner(string: c).scanHexInt32(&ch)
var char = UInt8(ch)
data.append(&char, count: 1)
}
return data
}
use:
let data = dataWithHexString(hex: "68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
let string = String(data: data, encoding: .utf8) // "hello, world"
Swift 4 & Swift 5 implementation:
init?(hexString: String) {
let len = hexString.count / 2
var data = Data(capacity: len)
var i = hexString.startIndex
for _ in 0..<len {
let j = hexString.index(i, offsetBy: 2)
let bytes = hexString[i..<j]
if var num = UInt8(bytes, radix: 16) {
data.append(&num, count: 1)
} else {
return nil
}
i = j
}
self = data
}
Usage:
let data = Data(hexString: "0a1b3c4d")
Swift 5
extension Data {
init?(hex: String) {
guard hex.count.isMultiple(of: 2) else {
return nil
}
let chars = hex.map { $0 }
let bytes = stride(from: 0, to: chars.count, by: 2)
.map { String(chars[$0]) + String(chars[$0 + 1]) }
.compactMap { UInt8($0, radix: 16) }
guard hex.count / bytes.count == 2 else { return nil }
self.init(bytes)
}
}
Here is my Swift 5 way to do it:
does take care of "0x" prefixes
use subscript instead of allocated Array(), no C style [i+1] too
add .hexadecimal to String.data(using encoding:) -> Data?
.
String Extension:
extension String {
enum ExtendedEncoding {
case hexadecimal
}
func data(using encoding:ExtendedEncoding) -> Data? {
let hexStr = self.dropFirst(self.hasPrefix("0x") ? 2 : 0)
guard hexStr.count % 2 == 0 else { return nil }
var newData = Data(capacity: hexStr.count/2)
var indexIsEven = true
for i in hexStr.indices {
if indexIsEven {
let byteRange = i...hexStr.index(after: i)
guard let byte = UInt8(hexStr[byteRange], radix: 16) else { return nil }
newData.append(byte)
}
indexIsEven.toggle()
}
return newData
}
}
Usage:
"5413".data(using: .hexadecimal)
"0x1234FF".data(using: .hexadecimal)
Tests:
extension Data {
var bytes:[UInt8] { // fancy pretty call: myData.bytes -> [UInt8]
return [UInt8](self)
}
// Could make a more optimized one~
func hexa(prefixed isPrefixed:Bool = true) -> String {
return self.bytes.reduce(isPrefixed ? "0x" : "") { $0 + String(format: "%02X", $1) }
}
}
print("000204ff5400".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
print("0x000204ff5400".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
print("541".data(using: .hexadecimal)?.hexa() ?? "failed") // fails
print("5413".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
Here's a simple solution I settled on:
extension NSData {
public convenience init(hexString: String) {
var index = hexString.startIndex
var bytes: [UInt8] = []
repeat {
bytes.append(hexString[index...index.advancedBy(1)].withCString {
return UInt8(strtoul($0, nil, 16))
})
index = index.advancedBy(2)
} while index.distanceTo(hexString.endIndex) != 0
self.init(bytes: &bytes, length: bytes.count)
}
}
Usage:
let data = NSData(hexString: "b8dfb080bc33fb564249e34252bf143d88fc018f")
Output:
print(data)
>>> <b8dfb080 bc33fb56 4249e342 52bf143d 88fc018f>
Update 6/29/2016
I updated the initializer to handle malformed data (i.e., invalid characters or odd number of characters).
public convenience init?(hexString: String, force: Bool) {
let characterSet = NSCharacterSet(charactersInString: "0123456789abcdefABCDEF")
for scalar in hexString.unicodeScalars {
if characterSet.characterIsMember(UInt16(scalar.value)) {
hexString.append(scalar)
}
else if !force {
return nil
}
}
if hexString.characters.count % 2 == 1 {
if force {
hexString = "0" + hexString
}
else {
return nil
}
}
var index = hexString.startIndex
var bytes: [UInt8] = []
repeat {
bytes.append(hexString[index...index.advancedBy(1)].withCString {
return UInt8(strtoul($0, nil, 16))
})
index = index.advancedBy(2)
} while index.distanceTo(hexString.endIndex) != 0
self.init(bytes: &bytes, length: bytes.count)
}
Here is my take on converting hexadecimal string to Data using Swift 4:
extension Data {
private static let hexRegex = try! NSRegularExpression(pattern: "^([a-fA-F0-9][a-fA-F0-9])*$", options: [])
init?(hexString: String) {
if Data.hexRegex.matches(in: hexString, range: NSMakeRange(0, hexString.count)).isEmpty {
return nil // does not look like a hexadecimal string
}
let chars = Array(hexString)
let bytes: [UInt8] =
stride(from: 0, to: chars.count, by: 2)
.map {UInt8(String([chars[$0], chars[$0+1]]), radix: 16)}
.compactMap{$0}
self = Data(bytes)
}
var hexString: String {
return map { String(format: "%02hhx", $0) }.joined()
}
}
(I threw in a small feature for converting back to hex string I found in this answer)
And here is how you would use it:
let data = Data(hexString: "cafecafe")
print(data?.hexString) // will print Optional("cafecafe")
One more solution that is simple to follow and leverages swifts built-in hex parsing
func convertHexToBytes(_ str: String) -> Data? {
let values = str.compactMap { $0.hexDigitValue } // map char to value of 0-15 or nil
if values.count == str.count && values.count % 2 == 0 {
var data = Data()
for x in stride(from: 0, to: values.count, by: 2) {
let byte = (values[x] << 4) + values[x+1] // concat high and low bits
data.append(UInt8(byte))
}
return data
}
return nil
}
let good = "e01AFd"
let bad = "e0671"
let ugly = "GT40"
print("\(convertHexToBytes(good))") // Optional(3 bytes)
print("\(convertHexToBytes(bad))") // nil
print("\(convertHexToBytes(ugly))") // nil
The code worked for me in Swift 3.0.2.
extension String {
/// Expanded encoding
///
/// - bytesHexLiteral: Hex string of bytes
/// - base64: Base64 string
enum ExpandedEncoding {
/// Hex string of bytes
case bytesHexLiteral
/// Base64 string
case base64
}
/// Convert to `Data` with expanded encoding
///
/// - Parameter encoding: Expanded encoding
/// - Returns: data
func data(using encoding: ExpandedEncoding) -> Data? {
switch encoding {
case .bytesHexLiteral:
guard self.characters.count % 2 == 0 else { return nil }
var data = Data()
var byteLiteral = ""
for (index, character) in self.characters.enumerated() {
if index % 2 == 0 {
byteLiteral = String(character)
} else {
byteLiteral.append(character)
guard let byte = UInt8(byteLiteral, radix: 16) else { return nil }
data.append(byte)
}
}
return data
case .base64:
return Data(base64Encoded: self)
}
}
}
Swift 5
With support iOS 13 and iOS2...iOS12.
extension String {
var hex: Data? {
var value = self
var data = Data()
while value.count > 0 {
let subIndex = value.index(value.startIndex, offsetBy: 2)
let c = String(value[..<subIndex])
value = String(value[subIndex...])
var char: UInt8
if #available(iOS 13.0, *) {
guard let int = Scanner(string: c).scanInt32(representation: .hexadecimal) else { return nil }
char = UInt8(int)
} else {
var int: UInt32 = 0
Scanner(string: c).scanHexInt32(&int)
char = UInt8(int)
}
data.append(&char, count: 1)
}
return data
}
}
Swift 5
There is a compact implementation of initialize Data instance from hex string using a regular expression. It searches hex numbers inside a string and combine them to a result data so that it can support different formats of hex representations:
extension Data {
private static let regex = try! NSRegularExpression(pattern: "([0-9a-fA-F]{2})", options: [])
/// Create instance from string with hex numbers.
init(from: String) {
let range = NSRange(location: 0, length: from.utf16.count)
let bytes = Self.regex.matches(in: from, options: [], range: range)
.compactMap { Range($0.range(at: 1), in: from) }
.compactMap { UInt8(from[$0], radix: 16) }
self.init(bytes)
}
/// Hex string representation of data.
var hex: String {
map { String($0, radix: 16) }.joined()
}
}
Examples:
let data = Data(from: "0x11223344aabbccdd")
print(data.hex) // Prints "11223344aabbccdd"
let data2 = Data(from: "11223344aabbccdd")
print(data2.hex) // Prints "11223344aabbccdd"
let data3 = Data(from: "11223344 aabbccdd")
print(data3.hex) // Prints "11223344aabbccdd"
let data4 = Data(from: "11223344 AABBCCDD")
print(data4.hex) // Prints "11223344aabbccdd"
let data5 = Data(from: "Hex: 0x11223344AABBCCDD")
print(data5.hex) // Prints "11223344aabbccdd"
let data6 = Data(from: "word[0]=11223344 word[1]=AABBCCDD")
print(data6.hex) // Prints "11223344aabbccdd"
let data7 = Data(from: "No hex")
print(data7.hex) // Prints ""
Handles prefixes
Ignores invalid characters and incomplete bytes
Uses Swift built in hex character parsing
Doesn't use subscripts
extension Data {
init(hexString: String) {
self = hexString
.dropFirst(hexString.hasPrefix("0x") ? 2 : 0)
.compactMap { $0.hexDigitValue.map { UInt8($0) } }
.reduce(into: (data: Data(capacity: hexString.count / 2), byte: nil as UInt8?)) { partialResult, nibble in
if let p = partialResult.byte {
partialResult.data.append(p + nibble)
partialResult.byte = nil
} else {
partialResult.byte = nibble << 4
}
}.data
}
}
Supposing your string is even size, you can use this to convert to hexadecimal and save it to Data:
Swift 5.2
func hex(from string: String) -> Data {
.init(stride(from: 0, to: string.count, by: 2).map {
string[string.index(string.startIndex, offsetBy: $0) ... string.index(string.startIndex, offsetBy: $0 + 1)]
}.map {
UInt8($0, radix: 16)!
})
}