Sorting of an array alphabetically in swift - ios

I am new to swift.I am trying one sample app in which I need to implement the sorting of an array in alphabetical order.I getting the json data and I am adding the titles in the array.Now i would like to sort that alphabetically.Here is my code .....
func updateSearchResults(data: NSData?)
{
do
{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
if let blogs: NSArray = json["results"] as? [AnyObject] {
print(blogs)
for blog in blogs {
if let name = blog["original_title"] as? String {
names.addObject(name)
}
}
print(names)
**let sortedArray = sorted(names, {
(str1: String, str2: String) -> Bool in
return str1.toInt() < str2.toInt()** // Here I am getting the Error Message
})
}
}
catch {
print("error serializing JSON: \(error)")
}
}
The error message I am getting is "Cannot invoke 'sorted' with an argument list of type '(NSMutableArray, (String, String) -> Bool)'"
I tried a lot to achieve this but I didn't find the solution.
Can anyone help me to resolve this issue.
Thanks In Advance.

First convert NSMutableArray to the Array by using below line of code.
let swiftArray = mutableArray as AnyObject as! [String]
Use below line of code to sort the Array.
var sortedArray = names.sorted { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }
Check below link for sort Closures.
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
Update for Swift 3.0
var sortedArray = swiftArray.sorted { $0.localizedCaseInsensitiveCompare($1) == ComparisonResult.orderedAscending }

Use this simple code of line to sort ur array
let sortedNames = names.sort { $0.name < $1.name }
For Swift 4 you can use only this
let sortedNames = names.sorted(by: <)

Swift4
var names = [ "Alpha", "alpha", "bravo", "beta"]
var sortedNames = names.sorted { $0.localizedCaseInsensitiveCompare($1) == ComparisonResult.orderedAscending }
print(sortedNames) //Logs ["Alpha", "alpha","beta", "bravo"]

Swift 4(working code)
JSON response -> Stored in aryNameList
"DATA": [
{
email = "iosworker#gmail.com";
firstname = Harvey
},
{
email = "poonam#openxcell.com";
firstname = poonam
},
{
email = "t#t.com";
firstname = rahul
},
{
email = "android.testapps#gmail.com";
firstname = Chulbulx
},
{
email = "t#t2.com";
firstname = rahul
},
{
email = "jaystevens32#gmail.com";
firstname = Jay
},
{
email = "royronald47#gmail.com";
firstname = Roy
},
{
email = "regmanjones#hotmail.com";
firstname = Regan
},
{
email = "jd#gmail.com";
firstname = Jaydip
}
]
Code
self.aryNameList = self.aryNameList.sorted(by: { (Obj1, Obj2) -> Bool in
let Obj1_Name = Obj1.firstname ?? ""
let Obj2_Name = Obj2.firstname ?? ""
return (Obj1_Name.localizedCaseInsensitiveCompare(Obj2_Name) == .orderedAscending)
})
working every case (for ex: lowerCase, upperCase..)

For an array of objects:
items = items.sorted(by: { (item1, item2) -> Bool in
return item1.product.name.compare(item2.product.name) == ComparisonResult.orderedAscending
})

Try this one
var names = [ "Alpha", "alpha", "bravo"]
var sortedNames = names.sort { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }
print(sortedNames) //Logs ["Alpha", "alpha", "bravo"]

Swift 3 solution:
let yourStringArray = [ "beTA", "ALPha", "Beta", "Alpha"]
var sortedArray = yourStringArray.sorted()
// Result will be ["ALPha", "Alpha", "Beta", "beTA"]
Creds to jjatie

Related

How to append JSON values into array using Swift 4?

I am trying to get JSON values and appending into array. Here, below code add_user_product have a chance to come null. If it is null need to append null into array and if not null need to store ID also.
I am trying to get output like - [10,null,12,13,null,……]
// add_user_products & If add_user_product == null need to store null otherwise add_user_product ["id"]
if let add_user_product = fields[“add_user_product"] as? [String : Any] {
let add_id = add_user_product["id"] as! Int
self.addlistIDData.append(add_id)
}
else {
//print("Failure")
}
below my sample response
{
"students":[
{
"grade":0,
"add_user_product":
{
"id":10
}
},
{
"grade":1,
"add_user_product":null
},
{
"grade":2,
"add_user_product":
{
"id":11
}
}
]
}
Expected output: [10,null,11,......] //This array I am going to use Tableview cell
I suggest use nil instead of null string.
Declare your addlistIDData type as [Int?] where Int is an Optional.
Consider below example I have created for you:
var addlistIDData: [Int?] = [10, nil, 12, 13, nil] //Created array just for example
//created dict for testing purpose
let fields: [String : Any]? = ["add_user_product": ["id": nil]]
if let field = fields {
if let add_user_product = field["add_user_product"] as? [String:Any] {
let add_id = add_user_product["id"] as? Int
//now append your object here weather it's a nil or it has any value
addlistIDData.append(add_id)
}
}
else {
//print("Failure")
}
print(addlistIDData)
And output will be:
[Optional(10), nil, Optional(12), Optional(13), nil, nil]
PS: You need to cast an object with if let or with guard let whenever you are accessing objects from this addlistIDData array.
null will not be identifiable, the only way to store it in your array would be to store it as String, But for that also you'll have to store othere elements as String.
But i would suggest instead of adding null just add 0 as:
var arr = [Int]()
if let add_user_product = fields["add_user_product"] as? [String: Any] {
if let productId = add_user_product["id"] as? Int {
arr.append(productId)
} else {
arr.append(0)
}
} else {
//
}
You can do like this:
var resultArray = [Int?]()
if let add_user_product = fields["add_user_product"] as? [String: Any] {
if let add_id = add_user_product["id"] as? Int {
resultArray.append(add_id)
} else {
resultArray.append(nil)
}
} else {
//print("Failure")
}
Hope this Helps.
You can use compactMap:
let arrayDict = [ ["id" : 3], ["id" : nil], ["id" : 5] ]
let result = arrayDict.compactMap { $0["id"] }
print(result)
Output:
[Optional(3), nil, Optional(5)]

How to get custom value back from Spotlight with CSCustomAttributeKey

I am trying to get some data back from Core Spotlight which I am storing using a custom attribute key. Tested this on macOS and iOS as well, the result is always the same.
My test class:
import CoreSpotlight
class SpotlightSearch {
let domainId = "com.company.some"
let originalDataKeyName: String
init() {
self.originalDataKeyName = domainId.replacingOccurrences(of: ".", with: "_") + "_originalData"
}
func addToIndex(title: String, content: String) {
guard let originalDataKey = CSCustomAttributeKey(keyName: originalDataKeyName, searchable: false, searchableByDefault: false, unique: false, multiValued: false)
else { return }
let uniqueId = "MyUniqueId" + title
let originalContent = NSString(string: content)
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String)
attributeSet.title = title
attributeSet.setValue(originalContent, forCustomKey: originalDataKey)
let item = CSSearchableItem(uniqueIdentifier: uniqueId, domainIdentifier: domainId, attributeSet: attributeSet)
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if let error = error {
print("Indexing error: \(error.localizedDescription)")
} else {
print("Item '\(title)' successfully indexed!")
}
}
}
var query: CSSearchQuery?
func search(title: String) {
var allItems = [CSSearchableItem]()
let queryString = "title == '\(title)'cd"
let attributes = [ "title", originalDataKeyName ]
let newQuery = CSSearchQuery(queryString: queryString, attributes: attributes)
newQuery.foundItemsHandler = { (items: [CSSearchableItem]) -> Void in
allItems.append(contentsOf: items)
}
newQuery.completionHandler = { [weak self] (error: Error?) -> Void in
guard let originalDataKeyName = self?.originalDataKeyName,
let originalDataKey = CSCustomAttributeKey(keyName: originalDataKeyName)
else { return }
print("Search complete")
for item in allItems {
let attributeSet = item.attributeSet
let customData = attributeSet.value(forCustomKey: originalDataKey)
// Always nil
if customData == nil {
print("\(String(describing: originalDataKeyName)) not found in \(attributeSet.description)")
} else if let originalData = customData as? NSData {
let data = Data(referencing: originalData)
if let originalString = String(data: data, encoding: .utf8) {
print("Found '\(originalString)'")
}
}
}
}
query = newQuery
newQuery.start()
}
}
On app init:
let newSpotlightSearch = SpotlightSearch()
newSpotlightSearch.addToIndex(title: "Banana", content: "🍌")
Later:
spotlightSearch.search(title: "Banana")
It will find the title, but will not give me back the custom attribute value. If I put a breakpoint after "// Always nil" and use po attributeSet I will get
(lldb) po attributeSet
{
"_kMDItemBundleID" = "de.axelspringer.SearchMac";
"_kMDItemDomainIdentifier" = "com.company.some";
"_kMDItemExpirationDate" = "2018-08-26 00:00:00 +0000";
"_kMDItemExternalID" = MyUniqueIdBanana;
"com_company_some_originalData" = "\Ud83c\Udf4c";
kMDItemTitle = Banana;
}
So the value is there, but Spotlight will not return it to me. Already tried to use NSData instead of NSString for the custom attribute, but same result.
Also found this orphaned question in the Apple developer forums:
CSCustomAttributeKey valueForCustomKey not working
I believe it's iOS issue.
While it's not fixed, maybe Apple will allow you to use a private API to do your thing.
So, attributeSet has private Dictionaries attributes and customAttributes. You can try to get those values using Key Value Coding and ObjC:
NSDictionary *attributes = [attributeSet valueForKey:#"attributes"];
id customData = attributes[originalDataKeyName];
OR
NSDictionary *customAttributes = [attributeSet valueForKey:#"customAttributes"];
id customData = customAttributes[originalDataKeyName];
Key type in those dictionaries is either NSString* or CSCustomAttributeKey*, so you can try supplying both originalDataKeyName and originalDataKey.

Swift - Append to NSMutableDictionary

I am trying to append to an NSMutableDictionary with the following code:
let RSVPDirectory = NSMutableDictionary()
for i in 0..<self.RSVPs.count {
var tmp = self.RSVPs[i]
var firstLetter = String()
if(tmp["lastname"] is NSNull)
{
firstLetter = ((tmp["email"] as? NSString)?.substring(to: 1).uppercased())!
}
else
{
firstLetter = ((tmp["lastname"] as? NSString)?.substring(to: 1).uppercased())!
}
if RSVPDirectory[firstLetter] == nil {
RSVPDirectory[firstLetter] = [AnyHashable]()
}
RSVPDirectory[firstLetter] = tmp
}
My problem with this is that I am expecting multiple tmp inside RSVPDirectory[firstLetter] but it only adds the first one as if its overriding the previous tmp
How do I append to NSMutableDictionary in swift, I know in objective-c you can do this:
[[RSVPDirectory objectForKey:firstLetter] addObject:tmp];
What would be the equivalent to that in swift?
Try the below code in a playground you will see the output, hope this gives you an idea.
func upperCaseFirstLetter(_ str: String) -> String {
guard let first = str.first else { return "" }
return "\(first)".uppercased()
}
var RSVPs = [[String:String]]()
var RSVPDirectory = [String: [[String:String]]]()
//Test Data
var str = ["email":"test1#c.com"]
RSVPs.append(str)
str = ["lastname":"Atest2"]
RSVPs.append(str)
for i in 0..<RSVPs.count {
var tmp = RSVPs[i]
var firstLetter = ""
if(tmp["lastname"] == nil) {
firstLetter = upperCaseFirstLetter(tmp["email"]!)
} else {
firstLetter = upperCaseFirstLetter(tmp["lastname"]!)
}
if RSVPDirectory[firstLetter] == nil {
RSVPDirectory[firstLetter] = [[String:String]]()
}
RSVPDirectory[firstLetter]?.append(tmp)
}
print(RSVPDirectory)
This is the native Swift version of your Objective-C-ish code.
It uses the Dictionary(grouping API of Swift 4
let RSVPDirectory = Dictionary(grouping: RSVPs) { (dictionary) -> String in
if let lastName = dictionary["lastname"] as? String {
return String(lastName.prefix(1).uppercased())
} else if let email = dictionary["email"] as? String {
return String(email.prefix(1).uppercased())
} else {
return "🚫"
}
}
Yes you are actually replacing the RSVPDirectory[firstLetter], overriding it every time with new tmp.
What you are looking for is this:
//RSVPDirectory[firstLetter] = tmp //Replace this line with below code
let tempArray = RSVPDirectory[firstLetter] as? [AnyHashable]
tempArray?.append(tmp)
RSVPDirectory[firstLetter] = tmpArray
Here I have used a tempArray because we want to mutate the array. Accessing it directly and trying to append new value will in-turn try to mutate an immutable value. So first I have got the array in the tempArray and then after mutating the array I swapped it back in the dictionary with updated values.

Remove duplicates from array of dictionaries, Swift 3

Problem
I have an array of dictionaries as follows:
var arrayOfDicts = [
["Id":"01", "Name":"Alice", "Age":"15"]
["Id":"02", "Name":"Bob", "Age":"53"]
["Id":"03", "Name":"Cathy", "Age":"12"]
["Id":"04", "Name":"Bob", "Age":"83"]
["Id":"05", "Name":"Denise", "Age":"88"]
["Id":"06", "Name":"Alice", "Age":"44"]
]
I need to remove all dictionaries where there is a duplicate name. For instance, I need an output of:
var arrayOfDicts = [
["Id":"01", "Name":"Alice", "Age":"15"]
["Id":"02", "Name":"Bob", "Age":"53"]
["Id":"03", "Name":"Cathy", "Age":"12"]
["Id":"05", "Name":"Denise", "Age":"88"]
]
Order does not need to be preserved.
Attempted Solution
for i in 0..<arrayOfDicts.count
{
let name1:String = arrayOfDicts[i]["Name"]
for j in 0..<arrayOfDicts.count
{
let name2:String = arrayOfDicts[j]["Name"]
if (i != j) && (name1 == name2)
{
arrayOfDicts.remove(j)
}
}
}
This crashes though, I believe since I am modifying the size of arrayOfDicts, so eventually it j is larger than the size of the array.
If someone could help me out, that would be much appreciated.
I definitely recommend having a new copy rather than modifying the initial array. I also create storage for names already used, so you should only need to loop once.
func noDuplicates(_ arrayOfDicts: [[String: String]]) -> [[String: String]] {
var noDuplicates = [[String: String]]()
var usedNames = [String]()
for dict in arrayOfDicts {
if let name = dict["name"], !usedNames.contains(name) {
noDuplicates.append(dict)
usedNames.append(name)
}
}
return noDuplicates
}
You can use a set to control which dictionaries to add to the resulting array. The approach it is very similar to the one used in these answer and this
let array: [[String : Any]] = [["Id":"01", "Name":"Alice", "Age":"15"],
["Id":"02", "Name":"Bob", "Age":"53"],
["Id":"03", "Name":"Cathy", "Age":"12"],
["Id":"04", "Name":"Bob", "Age":"83"],
["Id":"05", "Name":"Denise", "Age":"88"],
["Id":"06", "Name":"Alice", "Age":"44"]]
var set = Set<String>()
let arraySet: [[String: Any]] = array.compactMap {
guard let name = $0["Name"] as? String else { return nil }
return set.insert(name).inserted ? $0 : nil
}
arraySet // [["Name": "Alice", "Age": "15", "Id": "01"], ["Name": "Bob", "Age": "53", "Id": "02"], ["Name": "Cathy", "Age": "12", "Id": "03"], ["Name": "Denise", "Age": "88", "Id": "05"]]
Please check this answer:
var arrayOfDicts = [
["Id":"01", "Name":"Alice", "Age":"15"],
["Id":"02", "Name":"Bob", "Age":"53"],
["Id":"03", "Name":"Cathy", "Age":"12"],
["Id":"04", "Name":"Bob", "Age":"83"],
["Id":"05", "Name":"Denise", "Age":"88"],
["Id":"06", "Name":"Alice", "Age":"44"]
]
var answerArray = [[String:String]]()
for i in 0..<arrayOfDicts.count
{
let name1 = arrayOfDicts[i]["Name"]
if(i == 0){
answerArray.append(arrayOfDicts[i])
}else{
var doesExist = false
for j in 0..<answerArray.count
{
let name2:String = answerArray[j]["Name"]!
if name1 == name2 {
doesExist = true
}
}
if(!doesExist){
answerArray.append(arrayOfDicts[i])
}
}
}
Several good answers already, but it was a fun exercise, so here's my solution. I'm assuming you don't care which of the duplicate entries are kept (this will keep the last one of the dupes).
func noDuplicates(arrayOfDicts: [[String:String]]) -> [[String:String]]
{
var noDuplicates: [String:[String:String]] = [:]
for dict in arrayOfDicts
{
if let name = dict["name"]
{
noDuplicates[name] = dict
}
}
// Returns just the values of the dictionary
return Array(noDuplicates.values.map{ $0 })
}
let uniqueArray = Array(Set(yourArrayWithDuplicates))
That should do the trick.
If you want to use just the name for uniqueness then create these as structs.
You shouldn't be doing anything with dictionaries. Much easier to work with data that makes sense.
Try this:
var uniqueNames = [String: [String:String] ]()
for air in arrayOfDicts {
if (uniqueNames[arr["Name"]!] == nil) {
uniqueNames[arr["Name"]!] = arr
}
}
result = Array(uniqueNames.values)
If you don't mind using an additional list:
var uniqueArray = [[String: String]]()
for item in arrayOfDicts {
let exists = uniqueArray.contains{ element in
return element["Name"]! == item["Name"]!
}
if !exists {
uniqueArray.append(item)
}
}

Filter array of strings, including "like" condition

If my main array is ["Hello","Bye","Halo"], and I'm searching for "lo", it will filter the array only to ["Hello", "Halo"].
This is what I've tried:
let matchingTerms = filter(catalogNames) {
$0.rangeOfString(self.txtField.text!, options: .CaseInsensitiveSearch) != nil
}
It throws
Type of expression is ambiguous without more context
Any suggestions?
Use contains instead:
let arr = ["Hello","Bye","Halo"]
let filtered = arr.filter { $0.contains("lo") }
print(filtered)
Output
["Hello", "Halo"]
Thanks to #user3441734 for pointing out that functionality is of course only available when you import Foundation
In Swift 3.0
let terms = ["Hello","Bye","Halo"]
var filterdTerms = [String]()
func filterContentForSearchText(searchText: String) {
filterdTerms = terms.filter { term in
return term.lowercased().contains(searchText.lowercased())
}
}
filterContentForSearchText(searchText: "Lo")
print(filterdTerms)
Output
["Hello", "Halo"]
Swift 3.1
let catalogNames = [ "Hats", "Coats", "Trousers" ]
let searchCatalogName = "Hats"
let filteredCatalogNames = catalogNames.filter { catalogName in
return catalogName.localizedCaseInsensitiveContains(searchCatalogName)
}
print(filteredCatalogNames)
my try...
let brands = ["Apple", "FB", "Google", "Microsoft", "Amazon"]
let b = brands.filter{(x) -> Bool in
(x.lowercased().range(of: "A".lowercased()) != nil)
}
print(b) //["Apple", "Amazon"]
with help of String extension you can use pure Swift solution (without import Foundation). I didn't check the speed, but it shouldn't be worse as the foundation equivalent.
extension String {
func contains(string: String)->Bool {
guard !self.isEmpty else {
return false
}
var s = self.characters.map{ $0 }
let c = string.characters.map{ $0 }
repeat {
if s.startsWith(c){
return true
} else {
s.removeFirst()
}
} while s.count > c.count - 1
return false
}
}
let arr = ["Hello","Bye","Halo"]
let filtered = arr.filter { $0.contains("lo") }
print(filtered) // ["Hello", "Halo"]
"a".contains("alphabet") // false
"alphabet".contains("") // true
You also need to compare to NSNotFound. The documentation for rangeOfString:options: says:
An NSRange structure giving the location and length in the receiver of the first occurrence of aString, modulo the options in mask. Returns {NSNotFound, 0} if aString is not found or is empty (#"").
import Foundation
let catalogNames = [ "Hats", "Coats", "Trousers" ]
let matchingTerms = catalogNames.filter {
$0.rangeOfString(self.txtField.text!, options: .CaseInsensitiveSearch).location != NSNotFound
}
Swift 5:
Considered below example. Filtering (case insensitive) out data from array 1 and populating 2nd array with that
let data = ["Apple", "Oranges", "Banana", "Grapes"]
var filteredData = [String]()
func filterText(_ text: String?) {
guard let text = text else {return}
filteredData.removeAll()
for element in data {
if element.lowercased().starts(with: text.lowercased()){
filteredData.append(element)
}
}
}

Resources