String interpretation in Swift - ios

I have a struct that looks like this:
struct colorShapeSize{
let color: String!
let shape: String!
let size: UIImage!
}
and I have a string that looks something like this:
"color:{Blue}shape:{round}size:{medium}"
All of the strings will be in the same format (i.e. color will always come first, shape second, and size third).
How would I extract the data from the string and put it into a colorShapeSize struct?

How about this?
struct ColorShapeSize {
let color: String
let shape: String
let size: String
init(rawValue: String) {
var dictionary: [String: String] = [:]
var sorted = rawValue.components(separatedBy: "}").filter({ return $0.components(separatedBy: ":{").count == 2 })
for s in sorted {
let kv = s.components(separatedBy: ":{")
let key = kv[0]
let value = kv[1]
dictionary[key] = value
}
color = dictionary["color"] ?? ""
shape = dictionary["shape"] ?? ""
size = dictionary["size"] ?? ""
}
}
let str = "color:{Blue}shape:{round}size:{medium}"
let css = ColorShapeSize(rawValue: str)
print(css.color, css.shape, css.size)

try this, it will extract the string in array, then you can do what you want with the value
func test() {
let givenString = "color:{Blue}shape:{round}size:{medium}"
var results = [String]()
do {
let regex = try NSRegularExpression(pattern: "\\{(.*?)\\}", options: [])
let tempString = givenString as NSString
regex.enumerateMatches(in: givenString, options: [], range: NSMakeRange(0, givenString.characters.count), using: { (result, flag, stop) in
if let range = result?.rangeAt(1) {
let number = tempString.substring(with: range)
results.append(number)
}
})
print(results) //["Blue", "round", "medium"] (Here you can initialize your struct with the values)
}
catch(let error) {
print("Unable to extract string : \(error.localizedDescription)")
}
}

Related

How to convert data into little endian format?

var val = 1240;
convert into little endian formate swift 3
Ex: 1500 (0x5DC) to 0xDC050000
let value = UInt16(bigEndian: 1500)
print(String(format:"%04X", value.bigEndian)) //05DC
print(String(format:"%04X", value.littleEndian)) //DC05
Make sure you are actually using the bigEndian initializer.
With 32-bit integers:
let value = UInt32(bigEndian: 1500)
print(String(format:"%08X", value.bigEndian)) //000005DC
print(String(format:"%08X", value.littleEndian)) //DC050000
If you want 1500 as an array of bytes in little-endian order:
var value = UInt32(littleEndian: 1500)
let array = withUnsafeBytes(of: &value) { Array($0) }
If you want that as a Data:
let data = Data(array)
Or, if you really wanted that as a hex string:
let string = array.map { String(format: "%02x", $0) }.joined()
let timeDevide = self.setmiliSecond/100
var newTime = UInt32(littleEndian: timeDevide)
let arrayTime = withUnsafeBytes(of: &newTime)
{Array($0)}
let timeDelayValue = [0x0B] + arrayTime
You can do something like
//: Playground - noun: a place where people can play
import UIKit
extension String {
func 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(location: 0, length: utf16.count)) { match, _, _ in
let byteString = (self as NSString).substring(with: match!.range)
var num = UInt8(byteString, radix: 16)!
data.append(&num, count: 1)
}
guard !data.isEmpty else { return nil }
return data
}
}
func convertInputValue<T: FixedWidthInteger>(_ inputValue: Data) -> T where T: CVarArg {
let stride = MemoryLayout<T>.stride
assert(inputValue.count % (stride / 2) == 0, "invalid pack size")
let fwInt = T.init(littleEndian: inputValue.withUnsafeBytes { $0.pointee })
let valuefwInt = String(format: "%0\(stride)x", fwInt).capitalized
print(valuefwInt)
return fwInt
}
var inputString = "479F"
var inputValue: Data! = inputString.hexadecimal()
let val: UInt16 = convertInputValue(inputValue) //9F47
inputString = "479F8253"
inputValue = inputString.hexadecimal()
let val2: UInt32 = convertInputValue(inputValue) //53829F47

How to trim a String using Swift 3

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)
}

How to iterate through Swift array and use data in iOS Chart?

I have a bar chart, which I am trying to populate through data pulled in from an array.
I am trying to iterate through this array in order to populate my chart.
Please note: in this example below there are 3 winningStreakDM variables hardcoded for testing purposes, but in 'real life' the number could be different each API call as it depends on how many 'users' are in that league.
This is why I need to iterate through the array and structure the data to suit.
let winningStreakDM1 : [String : Any] = [
"id" : 2,
"user_name" : "Dicky",
"winning_streak" : 5
]
let winningStreakDM2 : [String : Any] = [
"id" : 6,
"user_name" : "G",
"winning_streak" : 2
]
let winningStreakDM3 : [String : Any] = [
"id" : 5,
"user_name" : "Sultan",
"winning_streak" : 0
]
My issue is that I don't know how to iterate through the initial array to structure my data for it to work with the above code.
This is my full script:
import UIKit
import Charts
class CommunityLeagueStatsVC: UIViewController {
// GRAPHS *********
#IBOutlet weak var chartView: BarChartView!
var values = [BarChartDataEntry]()
// ****************
//********CHART VARIABLES**************//
//WINS LOSSES DRAWS
var winStreak: Double = 0.0
#IBOutlet weak var leagueStatsScrollView: UIScrollView!
var noDefeats: Bool?
var noWins: Bool?
var biggestWin: String?
var biggestWinTeams: String?
var passedCommunityName: String?
#IBOutlet weak var communityName: UILabel!
var playerId2: String?
var communityId2: String?
var eMail2: String?
override func viewDidLoad() {
super.viewDidLoad()
let winningStreak = ["Wins"]
let gamesWon = [winStreak]
setWinStreakChart(dataPoints: winningStreak, values: gamesWon)
let defaults = UserDefaults.standard
let Email = defaults.string(forKey: "userEmail")
let playerId = defaults.string(forKey: "playerId")
let commsId = defaults.string(forKey: "communityId")
self.playerId2 = playerId
self.communityId2 = commsId
self.eMail2 = Email
}
func setWinStreakChart(dataPoints: [String], values: [BarChartDataEntry]){
xAxis.valueFormatter = WinningStreakFormatter(chartView: self.chartView)
let barChartDataSet = BarChartDataSet(values: values, label: "Winning Streak")
barChartDataSet.colors = ChartColorTemplates.material()
let barChartData = BarChartData(dataSet: barChartDataSet)
barChartData.setValueFont(UIFont.systemFont(ofSize: 12.0))
self.chartView.data = barChartData
}
override func viewDidAppear(_ animated: Bool) {
let myUrl = URL(string: "http://www.xxx.uk/xxx/getLeagueStats.php")
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"
let postString = "player_id=\(self.playerId2!)&community_id=\(communityId2!)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
DispatchQueue.main.async
{
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:AnyObject]
print (json!)
if let dict = json?["leagueStats"] as? [String:AnyObject] {
//WINNING STREAK
var values = [BarChartDataEntry]()
if let dataWinStreak = dict["winningStreak"] as? [[String : Any]] {
print ("test one")
for (index,item) in dataWinStreak.enumerated() {
if let yValue = item["winning_streak"] as? Int, let userName = item["user_name"] as? String {
print ("test two")
let barChartDataEntry = BarChartDataEntry(x: Double(index), y: Double(yValue), data: userName as AnyObject?)
values.append(barChartDataEntry)
}
}
self.setWinStreakChart(dataPoints: ["wins"], values: values)
}
catch{
print(error)
}
}
}
task.resume()
}
}
UPDATE:
These are the errors that I am currently receiving:
With a bit of playing around and commenting out the line containing the first error I have got to this stage which correctly shows a graph with values, but no xAxis containing the userNames of each player.
You can create array of BarChartDataEntry this way.
var values = [BarChartDataEntry]()
if let dataWinStreak = dict["winningStreak"] as? [[String : Any]] {
for (index,item) in dataWinStreak.enumerated() {
if let yValue = item["winning_streak"] as? Int, let userName = item["user_name"] as? String {
let barChartDataEntry = BarChartDataEntry(x: index, y: yValue, data: userName)
values.append(barChartDataEntry)
}
}
}
//Now use values array
Edit: You just need to change setWinStreakChart function because now you are working with dynamic data not static one.
func setWinStreakChart(dataPoints: [String], values: [BarChartDataEntry]){
let xAxis : XAxis = self.chartView.xAxis;
xAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: 10.0)!
xAxis.labelTextColor = UIColor.black
xAxis.drawAxisLineEnabled = false
xAxis.drawGridLinesEnabled = true
xAxis.granularity = 1;
xAxis.labelPosition = .bottom
xAxis.valueFormatter = WinningStreakFormatter(chartView: self.chartView)
let barChartDataSet = BarChartDataSet(values: values, label: "Winning Streak")
barChartDataSet.colors = ChartColorTemplates.material()
let barChartData = BarChartData(dataSet: barChartDataSet)
barChartData.setValueFont(UIFont.systemFont(ofSize: 12.0))
self.chartView.data = barChartData
}
And now call this function after the for loop where we are creating array of BarChartDataEntry.
self.setWinStreakChart(dataPoints: ["wins"], values: values)

Splitting a string in swift using multiple delimiters

I am trying to split (or explode) a string in Swift (1.2) using multiple delimiters, or seperators as Apple calls them.
My string looks like this:
KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value
I have formatted it for easy reading:
KEY1=subKey1=value&subkey2=value
KEY2=subkey1=value&subkey2=value
KEY3=subKey1=value&subkey3=value
The uppercase "KEY" are predefined names.
I was trying to do this using:
var splittedString = string.componentsSeparatedByString("KEY1")
But as you can see, I can only do this with one KEY as the separator, so I am looking for something like this:
var splittedString = string.componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"])
So the result would be:
[
"KEY1" => "subKey1=value&subkey2=value",
"KEY2" => "subkey1=value&subkey2=value",
"KEY3" => "subkey1=value&subkey2=value"
]
Is there anything built into Swift 1.2 that I can use?
Or is there some kind of extension/library that can do this easily?
Thanks for your time, and have a great day!
One can also use the following approach to split a string with multiple delimiters in case keys are single characters:
//swift 4+
let stringData = "K01L02M03"
let res = stringData.components(separatedBy: CharacterSet(charactersIn: "KLM"))
//older swift syntax
let res = stringData.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "KLM"));
res will contain ["01", "02", "03"]
If anyone knows any kind of special syntax to extend the approach to multiple characters per key you are welcome to suggest and to improve this answer
Swift 4.2 update to #vir us's answer:
let string = "dots.and-hyphens"
let array = string.components(separatedBy: CharacterSet(charactersIn: ".-"))
This isn't very efficient, but it should do the job:
import Foundation
extension String {
func componentsSeperatedByStrings(ss: [String]) -> [String] {
let inds = ss.flatMap { s in
self.rangeOfString(s).map { r in [r.startIndex, r.endIndex] } ?? []
}
let ended = [startIndex] + inds + [endIndex]
let chunks = stride(from: 0, to: ended.count, by: 2)
let bounds = map(chunks) { i in (ended[i], ended[i+1]) }
return bounds
.map { (s, e) in self[s..<e] }
.filter { sl in !sl.isEmpty }
}
}
"KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value".componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"])
// ["=subKey1=value&subkey2=value", "=subkey1=value&subkey2=value", "=subKey1=value&subkey3=value"]
Or, if you wanted it in dictionary form:
import Foundation
extension String {
func componentsSeperatedByStrings(ss: [String]) -> [String:String] {
let maybeRanges = ss.map { s in self.rangeOfString(s) }
let inds = maybeRanges.flatMap { $0.map { r in [r.startIndex, r.endIndex] } ?? [] }
let ended = [startIndex] + inds + [endIndex]
let chunks = stride(from: 0, to: ended.count, by: 2)
let bounds = map(chunks) { i in (ended[i], ended[i+1]) }
let values = bounds
.map { (s, e) in self[s..<e] }
.filter { sl in !sl.isEmpty }
let keys = filter(zip(maybeRanges, ss)) { (r, _) in r != nil }
var result: [String:String] = [:]
for ((_, k), v) in zip(keys, values) { result[k] = v }
return result
}
}
"KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value".componentsSeperatedByStrings(["KEY1", "KEY2", "KEY3"])
// ["KEY3": "=subKey1=value&subkey3=value", "KEY2": "=subkey1=value&subkey2=value", "KEY1": "=subKey1=value&subkey2=value"]
For Swift 2:
import Foundation
extension String {
func componentsSeperatedByStrings(ss: [String]) -> [String] {
let unshifted = ss
.flatMap { s in rangeOfString(s) }
.flatMap { r in [r.startIndex, r.endIndex] }
let inds = [startIndex] + unshifted + [endIndex]
return inds.startIndex
.stride(to: inds.endIndex, by: 2)
.map { i in (inds[i], inds[i+1]) }
.flatMap { (s, e) in s == e ? nil : self[s..<e] }
}
}
Swift 5:
extension String {
func components<T>(separatedBy separators: [T]) -> [String] where T : StringProtocol {
var result = [self]
for separator in separators {
result = result
.map { $0.components(separatedBy: separator)}
.flatMap { $0 }
}
return result
}
}
It's for the sack of nice and neat code, don't use it if you need something efficiently
Swift 2 for forward compatibility
Using a regular expression:
let string = "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value"
let nsString :NSString = string
let stringRange = NSMakeRange(0, string.utf16.count)
let pattern = "(KEY\\d)=([^=]+=[^&]+[^=]+?=[^K]+)"
var results = [String:String]()
do {
var regEx = try NSRegularExpression(pattern:pattern, options:[])
regEx.enumerateMatchesInString(string, options: [], range: stringRange) {
(result : NSTextCheckingResult?, _, _) in
if let result = result {
if result.numberOfRanges == 3 {
let key = nsString.substringWithRange(result.rangeAtIndex(1))
let value = nsString.substringWithRange(result.rangeAtIndex(2))
results[key] = value
}
}
}
}
catch {
print("Bad Pattern")
}
results: ["KEY3": "subKey1=value&subkey3=value", "KEY2": "subkey1=value&subkey2=value", "KEY1": "subKey1=value&subkey2=value"]
You could do it with regular expressions. The below snippet is a bit clumsy and not really fail-safe but it should give you an idea.
let string = "KEY1=subKey1=value&subkey2=valueKEY2=subkey1=value&subkey2=valueKEY3=subKey1=value&subkey3=value"
let re = NSRegularExpression(pattern: "(KEY1|KEY2|KEY3)=", options: nil, error: nil)!
let matches = re.matchesInString(string, options: nil,
range: NSMakeRange(0, count(string)))
var dict = [String: String]()
for (index, match) in enumerate(matches) {
let key = (string as NSString).substringWithRange(
NSMakeRange(match.range.location, match.range.length - 1))
let valueStart = match.range.location + match.range.length
let valueEnd = index < matches.count - 1 ? matches[index + 1].range.location
: count(string)
let value = (string as NSString).substringWithRange(
NSMakeRange(valueStart, valueEnd - valueStart))
dict[key] = value
}
The final value of dict is
[KEY3: subKey1=value&subkey3=value,
KEY2: subkey1=value&subkey2=value,
KEY1: subKey1=value&subkey2=value]

Save data from textfile in a array and/or dictionary

I hang on a programming step. I hope you can help me.
I got in a textfile the following rows:
#Objekt
Objektnr; 1000000;
Filialname; Dresden;
Filialeemail; email#email.com;
#Baustelle
Anschrift1;;
Anschrift2;Juwelier Schubert;
Strasse;Theresienstrafle 7;
Land;DE;
Ort;TheTown;
PLZ;12345;
....
I have the following function for bring the file-data to an array or an dictionary. In another function i will save the data to the local CoreData-Database.
func startImportTextfile(fileName: String, fileDir: String) -> Bool {
var filePath : String = folderDocuments.stringByAppendingPathComponent(fileDir)
var fileNameWithPath = filePath.stringByAppendingPathComponent(fileName)
var fullImportContent = String(contentsOfFile: fileNameWithPath, encoding: NSUTF8StringEncoding, error: nil)
if(fullImportContent != "")
{
var stringArray = fullImportContent!.componentsSeparatedByString("\n")
var stringArrayCompleteData = Dictionary<String, Array<Any>>()
var arrIndexSection : String = "NoHeader"
for singleRow in stringArray
{
if(singleRow != "")
{
switch singleRow {
case "#Header":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Objekt":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Baustelle":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Auftraggeber":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Architekt":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Vermittler":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Regulierer":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Versicherung":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Kontaktstellen":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
case "#Dateien":
arrIndexSection = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
default:
//Here the multiple array would be filled
var arrSingleRow = singleRow.componentsSeparatedByString(";")
if( arrSingleRow.count > 0 )
{
if( arrIndexSection == "Kontaktstellen" )
{
//TODO: Kontaktstellen einlesen
//#Kontaktstellen
//Baustelle;0;348873;;;;0
//Baustelle;0;381263;;Albrecht;0815;0
//Regulierer/SV;0;171979;Josef;Eder;08546/911055;0
println( "Kontaktstellendaten" )
println( singleRow )
}
else if( arrIndexSection == "Dateien" )
{
//TODO: Dateien einlesen
//#Dateien
//11022015090007_BEmail_INNNUE_21102014141534.pdf; 99; Email an asdfasdf#sdf.de
println( "Dateiendaten" )
println( singleRow )
}
else
{
stringArrayCompleteData[arrIndexSection] = [arrSingleRow[0]: arrSingleRow[1]]
}
}
}
}
}
for key in stringArrayCompleteData {
println("Key: \(key)")
}
return true
}
else
{
return false
}
}
The aim is that I can open the data like this:
println(stringArrayCompleteData["Objekt"].Objektnr)
But I dont know how i have to declare the stringArrayCompleteData.
Maybe i have to change this decleration
var stringArrayCompleteData = Dictionary<String, Array<Any>>()
to
var stringArrayCompleteData = Array<String, Dictionary<String, Any>>()
Thanks for every little help
Hi Roland – My suggestion would be to divide up the text into the different sections and put each into a struct. For example, you would define a Objekt struct that would contain 3 properties: objektnr, filialname, and filialeemail.
You could make a tree of structs, for example, with a root Person struct. The Person struct would contain properties corresponding to an Objekt struct instance, a Bastille struct instance, etc.
Here's some example code to define the structs:
struct Person:Printable {
init() {
}
var objektStruct:Objekt?
var baustelleStruct:Baustelle?
var description:String {
get {
let objektStructDescription = objektStruct != nil ? objektStruct!.description : ""
let baustelleStructDescription = baustelleStruct != nil ? baustelleStruct!.description : ""
return "\(objektStructDescription)\n\n\(baustelleStructDescription)"
}
}
}
struct Objekt:Printable {
var objektnr:Int = 0
var filialname:String = ""
var filialeemail:String = ""
init(text:String) {
// Divide the text into an array of lines
let textLinesArray:[String] = text.componentsSeparatedByString("\n")
// Loop through each line and set the corresponding property
for textLine:String in textLinesArray {
// Separate the components of the line at each ";" character
var components:[String] = textLine.componentsSeparatedByString(";")
components = components.map({ (componentString:String) -> String in
return componentString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
})
// Get the key and value for the line.
let key:String = components.count > 0 ? components[0] : ""
let value:String = components.count > 1 ? components[1] : ""
// Based on the key, set the appropriate property value
switch key {
case "Objektnr":
if let valueAsInt = value.toInt() {
objektnr = valueAsInt
}
case "Filialname":
filialname = value
case "Filialeemail":
filialeemail = value
default:
break
}
}
}
var description:String {
get {
return "*Objekt*\n Objektnr = \(objektnr)\n Filialname = \(filialname)\n Filialeemail = \(filialeemail)"
}
}
}
struct Baustelle:Printable {
var anschrift1:String = ""
var anschrift2:String = ""
var strasse:String = ""
var land:String = ""
var ort:String = ""
var plz:Int = 0
init(text:String) {
// Divide the text into an array of lines
let textLinesArray:[String] = text.componentsSeparatedByString("\n")
// Loop through each line and set the corresponding property
for textLine:String in textLinesArray {
// Separate the components of the line at each ";" character
var components:[String] = textLine.componentsSeparatedByString(";")
components = components.map({ (componentString:String) -> String in
return componentString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
})
// Get the key and value for the line.
let key:String = components.count > 0 ? components[0] : ""
let value:String = components.count > 1 ? components[1] : ""
// Based on the key, set the appropriate property value
switch key {
case "Anschrift1":
anschrift1 = value
case "Anschrift2":
anschrift2 = value
case "Strasse":
strasse = value
case "Land":
land = value
case "Ort":
ort = value
case "PLZ":
if let valueAsInt = value.toInt() {
plz = valueAsInt
}
default:
break
}
}
}
var description:String {
get {
return "*Baustelle*\n Anschrift1 = \(anschrift1)\n Anschrift2 = \(anschrift2)\n Strasse = \(strasse)\n Land = \(land)\n Ort = \(ort)\n PLZ = \(plz)"
}
}
}
I wrote the init methods for the Objekt and Baustelle so that the whole text section can be supplied, and the struct takes care of parsing it into the different properties.
Here's some sample code to use the above structs:
let text = String(contentsOfFile: "/Users/markstone/Downloads/textdata.txt")!
// Divide the text at each double newline into multiline sections.
// Eg., the arrayOfTextSections[0] will be the multiline string:
// #Objekt
// Objektnr; 1000000;
// Filialname; Dresden;
// Filialeemail; email#
let arrayOfTextSections = text.componentsSeparatedByString("\n\n")
// Create an empty Person struct instance. We'll fill it in the loop below
var person = Person()
for textSection in arrayOfTextSections {
// For each text section, find out the heading (eg., #Objekt or #Baustelle).
let sectionStringArray = textSection.componentsSeparatedByString("\n")
let sectionHeading = sectionStringArray[0]
switch sectionHeading {
case "#Objekt":
// Create a new instance of the Objekt using the multiline text in this section, and set this instance to the person.objektStruct property.
person.objektStruct = Objekt(text: textSection)
case "#Baustelle":
// Create a new instance of the Baustelle using the multiline text in this section, and set this instance to the person.baustelleStruct property.
person.baustelleStruct = Baustelle(text: textSection)
default:
break
}
}
print(person)
The line print(person) prints the following output:
*Objekt*
Objektnr = 0
Filialname = Dresden
Filialeemail = email#email.com
*Baustelle*
Anschrift1 =
Anschrift2 = Juwelier Schubert
Strasse = Theresienstrafle 7
Land = DE
Ort = TheTown
PLZ = 12345
With this sort of approach, it's quite easy to then turn the struct instances into managed objects for a core data store.
I placed a text file containing your data and saved in a text file in Document's Directory and passed the text filename and the directory path to the function and the functions returns the dictionary with the data into it in dictionary format.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let fileDirectory : [String] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as [String]
var fileDir = fileDirectory[0]
var returnedDictionary : Dictionary = self.startImportTextfile("textdata", fileDir: fileDir)
println(returnedDictionary)
var keyDictionary : Dictionary! = returnedDictionary["Objekt"]
println(keyDictionary["Objektnr"] as String!)
}
func startImportTextfile(fileName: String, fileDir: String) -> Dictionary<String,Dictionary<String,String>> {
let fileNameWithPath :String! = fileDir.stringByAppendingPathComponent(fileName)
var err: NSError?
var currentKey : String!
let fullImportContent :String! = String.stringWithContentsOfFile(fileNameWithPath!, encoding: NSUTF8StringEncoding, error: &err)
var dataDictionary = [String : Dictionary <String,String>]()
if(fullImportContent != "")
{
var singleLineArray = fullImportContent!.componentsSeparatedByString("\n")
for singleRow in singleLineArray
{
if(singleRow != "")
{
switch singleRow
{
case "#Header","#Objekt","#Baustelle","#Auftraggeber","#Architekt","#Vermittler","#Regulierer","#Versicherung","#Kontaktstellen","#Dateien":
currentKey = singleRow.stringByReplacingOccurrencesOfString("#", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
dataDictionary[currentKey] = Dictionary <String,String>()
default:
var arrayForSingleRow = singleRow.componentsSeparatedByString(";")
if( arrayForSingleRow.count > 0 )
{
var sampledict : Dictionary! = dataDictionary[currentKey]
sampledict[arrayForSingleRow[0]] = arrayForSingleRow[1]
dataDictionary[currentKey] = sampledict
}
}
}
}
}
return dataDictionary
}
}
After executing it i get the following output :
[Objekt: [Filialname: Dresden, Objektnr: 1000000, Filialeemail: email#], Baustelle: [PLZ: 12345, Ort: TheTown, Land: DE, Anschrift1: , Strasse: Theresienstrafle 7, Anschrift2: Juwelier Schubert]]
1000000
where Objekt is the key and it contains a dictionary value and from that dictionary you would be able to access Objektnr as i have done in viewDidLoad.
Link to Text file : https://www.dropbox.com/s/oyzbo128zr3jd6h/textdata?dl=0
It looks like your guess is correct and what you mean to declare stringArrayCompleteData as is indeed a dictionary of dictionaries, with Any as the value type for the dictionary. By the way, you can declare that type a bit more neatly as [String: [String:Any]]
Though, do you just want to store the details as strings? Or do you plan to parse numbers as integers? If you want some stronger-typed set of values, as an alternative to using Any as the data type, you might want to look into using an enum with associated values.
Here’s a version of your code that will store up the data records and insert them under the key. This could be neatened up a lot, but also uses a few Swift features you might find useful.
import Foundation
let folderDocuments = "/"
// rather than return true/false, you could return the data as an optional
func startImportTextfile(fileName: String, fileDir: String) -> [String: [String:String]]? {
let filePath: String = folderDocuments.stringByAppendingPathComponent(fileDir)
let fileNameWithPath = filePath.stringByAppendingPathComponent(fileName)
var error: NSError?
let fullImportContent = String(contentsOfFile: fileNameWithPath, encoding: NSUTF8StringEncoding, error: &error)
if let data = fullImportContent where !data.isEmpty {
let stringArray = split(data) { $0 == "\n" }
var completeData: [String: [String:String]] = [:]
var sectionEntries: [String:String] = [:]
var arrIndexSection: String? = nil
for singleRow in stringArray {
// first is an easy and safe way to check if first character
// has a specific value, whilst handling empty strings
if first(singleRow) == "#" {
// insert records from previous section
if let header = arrIndexSection {
completeData[header] = sectionEntries
}
// start a new section
// dropFirst removes the first "#"
arrIndexSection = dropFirst(singleRow)
sectionEntries = [:]
}
else {
let arrSingleRow = split(singleRow) { $0 == ";" }
if( arrSingleRow.count > 0 ) {
switch arrIndexSection {
case .Some("Kontaktstellen"):
//TODO: Kontaktstellen einlesen
println( "Kontaktstellendaten" )
println( singleRow )
case .Some("Dateien"):
//TODO: Dateien einlesen
println( "Dateiendaten" )
println( singleRow )
default:
if let label = first(arrSingleRow) {
sectionEntries[label] = first(dropFirst(arrSingleRow)) ?? ""
}
}
}
}
}
if let header = arrIndexSection {
completeData[header] = sectionEntries
}
return completeData
}
println("fail: \(error)")
return nil
}
if let data = startImportTextfile(Process.arguments[1], Process.arguments[2]) {
for (key,value) in data {
println("Key: \(key)\nData: \(value)")
}
}

Resources