Make categories from string in XML data - ios

I looked everywhere but I don't see anything specific to the thing I am trying to achieve, especially with iOS/swift. I would like to make age categories from the XML parser request I am calling in my app.
The age information will have to be called from the tag in the xml data:
<item>
<description>ANTHONY JOHNSON, Age: 17 From: PEORIA, IL</description></item>
I already have a call to get the description tag in full:
func parser(_ parser: XMLParser, didEndElement elementName: String,
namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "description" {
self.person.desc = self.foundCharacters; }
if elementName == "item" {
tempData.desc = self.person.desc; }
Item class:
class Item {
var desc = "";
}
I just need to make categories from the age value only. I plan on using them with an enum:
enum ageCategories: String {
case ageUnder10 = "Age 10 and under"
case age10Plus = "Age 10-15"
case age15Plus = "Age 15+"
}
My questions are:
Do I make a call in the XML parser to get the ages? How would I organize the call to get the ages part of the description string only and put them in categories?
OR
Is there an easier way to call just the ages from the string I have already called in the XML parser and then make categories with that? i.e: replacingOccurrences(range searchRange:"Age")

Your question boils down to finding an age in the description string. The following shows one solution using Scanner. This code assumes that the age will be a number after the text "Age:".
let desc = "ANTHONY JOHNSON, Age: 17 From: PEORIA, IL"
var ageRange: ageCategories? = nil
let scanner = Scanner(string: desc)
if scanner.scanUpTo("Age:", into: nil) && scanner.scanString("Age:", into: nil) {
var age = 0
if scanner.scanInt(&age) {
switch age {
case 0..<10:
ageRange = .AgeUnder10
case 10..<15:
ageRange = .Age10Plus
case 15...:
ageRange = .Age15Plus
default:
break
}
}
}
if let ageRange = ageRange {
print("Found age range of \(ageRange)")
} else {
print("No valid age found")
}
Adjust the ranges in the case statements to match your actual needs. The strings you are assigning to your enum values are ambiguous. Is 10 supposed to be .AgeUnder10 or .Age10Plus?
FYI - enum values should start with lowercase letters and the enum name should start with uppercase. So your enum should be:
enum AgeCategories: String {
case ageUnder10 = "Age under 10"
case age10Plus = "Age 10-14"
case age15Plus = "Age 15+"
}

Related

How to split string as English and non English using Swift 4?

I have a string which contains English and Arabic together. I am using an API, that is why I cannot set an indicator in it.
What I want to get is: the Arabic and English split into tow parts. Here is a sample String:
"بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants.",
I cannot find how to split it into 2 parts that I get Arabic and English into two different parts.
What I want:
so there can be any language, my problem is to only take out English or Arabic language and show them in respective fields.
How can I achieve it?
You can use a Natural Language Tagger, which would work even if both scripts are intermingled:
import NaturalLanguage
let str = "¿como? بداية start وسط middle начать средний конец نهاية end. 從中間開始. "
let tagger = NLTagger(tagSchemes: [.script])
tagger.string = str
var index = str.startIndex
var dictionary = [String: String]()
var lastScript = "other"
while index < str.endIndex {
let res = tagger.tag(at: index, unit: .word, scheme: .script)
let range = res.1
let script = res.0?.rawValue
switch script {
case .some(let s):
lastScript = s
dictionary[s, default: ""] += dictionary["other", default: ""] + str[range]
dictionary.removeValue(forKey: "other")
default:
dictionary[lastScript, default: ""] += str[range]
}
index = range.upperBound
}
print(dictionary)
and print the result if you'd like:
for entry in dictionary {
print(entry.key, ":", entry.value)
}
yielding :
Hant : 從中間開始.
Cyrl : начать средний конец
Arab : بداية وسط نهاية
Latn : ¿como? start middle end.
This is still not perfect since the language tagger only checks to which script the most number of letters in a word belong to. For example, in the string you're working with, the tagger would consider الصَّالِحِينَ.Bismika as one word. To overcome this, we could use two pointers and traverse the original string and check the script of words individually. Words are defined as contiguous letters:
let str = "بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants."
let tagger = NLTagger(tagSchemes: [.script])
var i = str.startIndex
var dictionary = [String: String]()
var lastScript = "glyphs"
while i < str.endIndex {
var j = i
while j < str.endIndex,
CharacterSet.letters.inverted.isSuperset(of: CharacterSet(charactersIn: String(str[j]))) {
j = str.index(after: j)
}
if i != j { dictionary[lastScript, default: ""] += str[i..<j] }
if j < str.endIndex { i = j } else { break }
while j < str.endIndex,
CharacterSet.letters.isSuperset(of: CharacterSet(charactersIn: String(str[j]))) {
j = str.index(after: j)
}
let tempo = String(str[i..<j])
tagger.string = tempo
let res = tagger.tag(at: tempo.startIndex, unit: .word, scheme: .script)
if let s = res.0?.rawValue {
lastScript = s
dictionary[s, default: ""] += dictionary["glyphs", default: ""] + tempo
dictionary.removeValue(forKey: "glyphs")
}
else { dictionary["other", default: ""] += tempo }
i = j
}
You can use the NaturalLanguageTagger as answered by #ielyamani but the only limitation is that it is iOS 12+
If you are trying to do this on earlier iOS versions, you can take a look at NSCharacterSet
You can create your own characterset to check whether a string has english characters and numbers
extension String {
func containsLatinCharacters() -> Bool {
var charSet = NSCharacterSet(charactersInString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
charSet = charSet.invertedSet
let range = (self as NSString).rangeOfCharacterFromSet(charSet)
if range.location != NSNotFound {
return false
}
return true
}
}
Another option is to use the charactersets already available:
let nonLatinString = string.trimmingCharacters(in: .alphanumerics)//symbols will still get through
let latinString = string.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)//symbols and non-latin characters wont get through
With these you can get the strings you want quite easily. But if these are not good enough, you can look to create your own characterset, use union, intersect etc to filter out the wanted and the unwanted characters.
Step 1:
You have to split whole string into an array by "." as I can see there are "." between sentence.
Step 2:
Pass each sentence to determine its language and append into different string.
Final Code
//add in your viewController
enum Language : String {
case arabic = "ar"
case english = "en"
}
override func viewDidLoad() {
super.viewDidLoad()
//make array of string
let kalmaArray = "بِاسْمِكَ رَبِّي وَضَعْتُ جَنْبِي، وَبِكَ أَرْفَعُهُ، فَإِنْ أَمْسَكْتَ نَفْسِي فَارْحَمْهَا، وَإِنْ أَرْسَلْتَهَا فَاحْفَظْهَا، بِمَا تَحْفَظُ بِهِ عِبَادَكَ الصَّالِحِينَ.Bismika rabbee wadaAAtu janbee wabika arfaAAuh, fa-in amsakta nafsee farhamha, wa-in arsaltaha fahfathha bima tahfathu bihi AAibadakas-saliheen. In Your name my Lord, I lie down and in Your name I rise, so if You should take my soul then have mercy upon it, and if You should return my soul then protect it in the manner You do so with Your righteous servants.".components(separatedBy: ".")
splitInLanguages(kalmaArray: kalmaArray)
}
private func splitInLanguages(kalmaArray: [String]){
var englishText = ""
var arabicText = ""
for kalma in kalmaArray {
if kalma.count > 0 {
if let language = NSLinguisticTagger.dominantLanguage(for: kalma) {
switch language {
case Language.arabic.rawValue:
arabicText.append(kalma)
arabicText.append(".")
break
default: // English
englishText.append(kalma)
englishText.append(".")
break
}
} else {
print("Unknown language")
}
}
}
debugPrint("Arabic: ", arabicText)
debugPrint("English: ", englishText)
}
I hope it will help you to split the string in two language. Let me know if you are still having any issue.

What's the appropriate way to access an enum from Swift Decodables?

I have a really weird case where I took JSON and thought I would be able to substring all the way to the data I want to access, however, it's not working as expected.
Using QuickType, I was able to convert this JSON: https://kidsuper.fodalabs.com/wp-json/wp/v2/art
To the below.
When trying to access, it seems like I should be able to do .acf.gallery.id however once I get to acf.gallery, it says .id does not exist. This is strange but here's what it returns when I try
let temp = imageArray[indexPath.row].acf.gallery.id
Value of type 'GalleryUnion?' has no member 'id'
Just for fun I tried this one and had no luck as well:
let temp = imageArray[indexPath.row].acf.GalleryUnion.galleryElementArray
Error
: Value of type 'Acf' has no member 'GalleryUnion'
The return when I print .acf.gallery starts like this:
Acf(company: "Season #3",
gallery: Optional(weddingszn.GalleryUnion.galleryElementArray([weddingszn.GalleryElement(
id: 135, galleryID: 135, title: "1-791x1024",
filename: "1-791x1024.jpg", url: "https://kidsuper.fodalabs.com/wp-content/up
Full code is below for what I'm trying to parse. Any ideas?
struct Acf: Codable {
let company: String
let gallery: GalleryUnion?
let tagline: String
let featuredImg: Bool?
enum CodingKeys: String, CodingKey {
case company, gallery, tagline
case featuredImg = "featured_img"
}
}
enum GalleryUnion: Codable {
case bool(Bool)
case galleryElementArray([GalleryElement])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode([GalleryElement].self) {
self = .galleryElementArray(x)
return
}
throw DecodingError.typeMismatch(GalleryUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for GalleryUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .galleryElementArray(let x):
try container.encode(x)
}
}
}
struct GalleryElement: Codable {
let id, galleryID: Int
let title, filename: String
let url: String
let alt, author, description, caption: String
let name, date, modified: String
let mimeType: MIMEType
let type: TypeEnum
let icon: String
let width, height: Int
let sizes: Sizes
enum CodingKeys: String, CodingKey {
case id = "ID"
case galleryID = "id"
case title, filename, url, alt, author, description, caption, name, date, modified
case mimeType = "mime_type"
case type, icon, width, height, sizes
}
}
You have to use if case, guard case or switch case to unpack the enum before you drill down into the array.
if case .galleryElementArray(let x) = imageArray[indexPath.row].acf.gallery {
print(x.first!.id)
}
however once I get to acf.gallery, it says .id does not exist. This is strange
No it isn't. According to your code, .gallery should be a GalleryUnion. Well, a GalleryUnion has no .id. It has no properties at all. A GalleryElement has an .id, but a GalleryUnion is not a GalleryElement. So I don't see where your surprise comes from; there is no surprise here.
A GalleryUnion has cases. You need to check which case this is. If it's . galleryElementArray you need to extract the associated value. Even then you won't have any .id, because what you will now have is still not a GalleryElement; it's an array of GalleryElements.
You could make this a lot easier on yourself by defining GalleryUnion with an extra calculated property that fetches the associated value for you:
enum GalleryUnion : Codable {
case bool(Bool)
case galleryElementArray([GalleryElement])
var galleryElements : [GalleryElement]? {
switch self {
case .bool : return nil
case let .galleryElementArray(arr): return arr
}
}
// ... and so on
}
That would allow you, at least, to say:
act.gallery.galleryElements?.map {$0.id}
...or whatever it is you have in mind.
So, GalleryUnion can one of two things. It can either both .bool(_) or galleryElementArray(_). When you want access the actual underlying value, you need to determine which state it's in.
To do this in Swift, you can use a switch statement. You can then use it to gain access to the internally contained values. Maybe something similar to:
if let gallery = acf.gallery {
switch gallery {
case .galleryElementArray(let values):
values.forEach {
print($0.id)
}
case .bool(_):
break
}
}
You might like to have a read of Enumerations, look for the "Associated Values" sections

Returning a processed String of an Enum on Swift iOS

I was just creating a localization module for my project, and as I'm new to Swift I had an idea of if the following was possible.
I have an enum like this:
enum Localizations : String
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var localized : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
With this enum I can have the localized string with this command:
let myString = Localizations.StringId1.localized
But when you have to put lots of strings the .localized it's like redundant as you already have Localizations before.
So what I'm looking for is if I can do something like this:
let myString = Localizations.StringId1
And myString would be something like "Press Button to Continue"
I have managed to do something working, but not in all cases.
Found in this link: https://appventure.me/2015/10/17/advanced-practical-enum-examples/
On the 'Advanced Enum Usage Protocols' Step it suggests a modification like the following would get what I want:
protocol CustomStringConvertible {
var description: String { get }
}
enum Trade: CustomStringConvertible {
case Buy, Sell
var description: String {
switch self {
case Buy: return "We're buying something"
case Sell: return "We're selling something"
}
}
}
let action = Trade.Buy
print("this action is \(action)")
// prints: this action is We're buying something
My modifications are theses ones:
protocol CustomEnumString {
var localized: String { get }
}
enum Localizations : String, CustomEnumString
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var localized : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
But when printing it if shows me the enum literal, and when passing to a function the compilers tells me it's invalid:
let localizedString = Localizations.StringId1
print("localization: \(localizedString)")
// prints: "localization: StringId1"
// note: AlertStrings is an struct with two strings
// this fails to compile saying that cannot convert value of type 'Localizations' to expected argument type 'String'
let alertStrings = AlertStrings(title: Localizations.StringId1, message: Localizations.StringId2)
// this one works, but it's not the purpose I had in mind
let alertStrings = AlertStrings(title: Localizations.StringId1.localized, message: Localizations.StringId2.localized)
So... in short, I would like to be able to do this:
let localizedString = Localizations.StringId1
print("localization: \(localizedString)")
// prints: "localization: Press Button To Continue"
let alertStrings = AlertStrings(title: Localizations.StringId1, message: Localizations.StringId2)
But in the enum I only would like to specify literals once, not first on case's and later inside a switch.
Thanks in advance!
phelgo, A member of NSBarcelona just told me about this, and works perfectly!
enum Localizations {
static let StringId1 = NSLocalizedString("string_to_translate_1", comment: "")
}
let myString = Localizations.StringId1
It may look unfamiliar to have an enum with no cases, but we get to
keep all of its safety (and code completion), while still preventing
Localizations from being instantiated by mistake (if it was a
struct)
Conform your enum to CustomStringConvertible and rename localized to description:
enum Localizations: String, CustomStringConvertible
{
case StringId1 = "string_to_translate_1"
case StringId2 = "string_to_translate_2"
case StringId3 = "string_to_translate_3"
var description : String {
return NSLocalizedString(self.rawValue, tableName: nil, bundle: NSBundle.mainBundle(), value: "", comment: "")
}
}
let alertStrings = AlertStrings(title: Localizations.StringId1.description, message: Localizations.StringId2.description)

swift text with 2 variables and different language

i would like to translate a string, which have two variables inside.
at the moment, i use for translating this code:
NSLocalizedString("Name_In_Langauge_String_File",comment:"")
but how can i translate the following string?
This is a test with 100 Pictures and 50 Users
where 100 and 50 are variables.
Put this in you Localizable.strings:
"Name_In_Langauge_String_File" = "This is a test with %d Pictures and %d Users";
and in your code:
String.localizedStringWithFormat(
NSLocalizedString("Name_In_Langauge_String_File",
comment: ""),
pictures,
users)
In a project I was working on I noticed that we kept repeating the code to do the string formatting for the localization file. This meant you could not just use the value, you first needed to check what parameters were required. One way to avoid this problem is to use Swift enums. This method is also useful for unit testing your localizations.
Assume you have the following 3 localizations in your strings file:
"TestNoParams" = "This is a test message";
"TestOneParam" = "Hello %#";
"TestTwoParams" = "This is a test with %d Pictures and %d Users";
Now you can use the following enum, protocol and extension to reference your strings:
protocol LocalizationProtocol {
var key: String { get }
var value: String { get }
}
extension LocalizationProtocol {
private func localizationValue() -> String {
return NSLocalizedString(key, comment:key)
}
private func localizationValueWithFormat(parameters: CVarArgType...) -> String {
return String(format: localizationValue(), arguments: parameters)
}
}
enum Localizations: LocalizationProtocol {
case TestNoParams
case TestOneParam(name: String)
case TestPicturesAndUsers(pictures: Int, users: Int)
var key: String {
switch self {
case .TestNoParams: return "TestNoParams"
case .TestOneParam: return "TestOneParam"
case .TestPicturesAndUsers: return "TestTwoParams"
}
}
var value: String {
switch self {
case .TestOneParam(let name):
return localizationValueWithFormat(name)
case .TestPicturesAndUsers(let pictures, let users):
return localizationValueWithFormat(pictures, users)
default:
return localizationValue()
}
}
}
Now to use it you just need to call the enums value method:
let testNoParam = Localizations.TestNoParams.value
let testOneParam = Localizations.TestOneParam(name: "users name").value
let testTwoParams = Localizations.TestPicturesAndUsers(pictures: 4, users: 500).value
The example I have shown is simplified, but you can also nest enums to provide a nice grouping for your localizations. For instance you could have your enums nested by ViewController. This is an example for a welcome message: Localizations.Main.WelcomeMessage.value

Swift switch statement for matching substrings of a String

Im trying to ask for some values from a variable.
The variable is going to have the description of the weather and i want to ask for specific words in order to show different images (like a sun, rain or so)
The thing is i have code like this:
if self.descriptionWeather.description.rangeOfString("Clear") != nil
{
self.imageWeather.image = self.soleadoImage
}
if self.descriptionWeather.description.rangeOfString("rain") != nil
{
self.imageWeather.image = self.soleadoImage
}
if self.descriptionWeather.description.rangeOfString("broken clouds") != nil
{
self.imageWeather.image = self.nubladoImage
}
Because when i tried to add an "OR" condition xcode gives me some weird errors.
Is it possible to do a swich sentence with that? Or anyone knows how to do add an OR condition to the if clause?
I had a similar problem today and realized this question hasn't been updated since Swift 1! Here's how I solved it in Swift 4:
switch self.descriptionWeather.description {
case let str where str.contains("Clear"):
print("clear")
case let str where str.contains("rain"):
print("rain")
case let str where str.contains("broken clouds"):
print("broken clouds")
default:
break
}
Swift 5 Solution
func weatherImage(for identifier: String) -> UIImage? {
switch identifier {
case _ where identifier.contains("Clear"),
_ where identifier.contains("rain"):
return self.soleadoImage
case _ where identifier.contains("broken clouds"):
return self.nubladoImage
default: return nil
}
}
You can do this with a switch statement using value binding and a where clause. But convert the string to lowercase first!
var desc = "Going to be clear and bright tomorrow"
switch desc.lowercaseString as NSString {
case let x where x.rangeOfString("clear").length != 0:
println("clear")
case let x where x.rangeOfString("cloudy").length != 0:
println("cloudy")
default:
println("no match")
}
// prints "clear"
Swift language has two kinds of OR operators - the bitwise ones | (single vertical line), and the logical ones || (double vertical line). In this situation you need a logical OR:
if self.descriptionWeather.description.rangeOfString("Clear") != nil || self.descriptionWeather.description.rangeOfString("clear") != nil {
self.imageWeather.image = self.soleadoImage
}
Unlike Objective-C where you could get away with a bitwise OR in exchange for getting a slightly different run-time semantic, Swift requires a logical OR in the expression above.
If you do this a lot, you can implement a custom ~= operator that defines sub-string matching. It lends itself to this nice syntax:
switch "abcdefghi".substrings {
case "def": // calls `"def" ~= "abcdefghi".substrings`
print("Found substring: def")
case "some other potential substring":
print("Found \"some other potential substring\"")
default: print("No substring matches found")
}
Implementation:
import Foundation
public struct SubstringMatchSource {
private let wrapped: String
public init(wrapping wrapped: String) {
self.wrapped = wrapped
}
public func contains(_ substring: String) -> Bool {
return self.wrapped.contains(substring)
}
public static func ~= (substring: String, source: SubstringMatchSource) -> Bool {
return source.contains(substring)
}
}
extension String {
var substrings: SubstringMatchSource {
return SubstringMatchSource(wrapping: self)
}
}
I'd recommend using a dictionary instead, as a mapping between the substring you're searching for and the corresponding image:
func image(for weatherString: String) -> UIImage? {
let imageMapping = [
"Clear": self.soleadoImage,
"rain": self.soleadoImage,
"broken clouds": self.nubladoImage]
return imageMapping.first { weatherString.contains($0.key) }?.value
}
A dictionary gives you flexibility, adding new mappings is easy to do.
This link also describes overloading operator ~= which is actually used by the switch statement for matching cases to allow you to match regular expressions.

Resources