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.
Related
How to use values globally parsed from JSONResponse. So, far able to extract the array from JSONResponse, but after that trying to put that array in an global array, not getting how to declare global array. somehow managed to do that but now not able to extract values index wise from that array. Please guide.
Code tried now:
if (JSONResponse["status"].intValue == 1)
{
if let fetchedImages = JSONResponse["data"].arrayObject {
self.arrResponseReceived = fetchedImages
}
print("self.arrResponseReceived", self.arrResponseReceived)
}
else
{
CommonMethodsClass.showAlertMessage(vc: self, titleStr: "Error!", messageStr: strMsg)
}
Code tried earlier:
arrResponseReceived = JSONResponse["data"].arrayValue
Global variable declared for array is:
var arrResponseReceived : [Any] = []
Data to parse is:
{
data = (
{
AverageRating = 4;
Image = "<null>";
IsActive = 1;
Latitude = "28.567806";
Longitude = "77.3236273";
SalonAddress = " Sec-58, India";
SalonEmail = "vy.chan#th-rce.com";
SalonID = 1;
SalonImage = "";
SalonMobile = 9999888877;
SalonName = Affinity;
SalonPhone = 9999888877;
TimeIntervalminutes = 20;
},
{
AverageRating = 5;
Image = "<null>";
IsActive = 1;
Latitude = "";
Longitude = "";
SalonAddress = "Mall, India";
SalonEmail = "rad#th-rce.com";
SalonID = 3;
SalonImage = "";
SalonMobile = 9999888877;
SalonName = Looks;
SalonPhone = 9999888877;
TimeIntervalminutes = 30;
}
);
message = "";
status = 1;
}
You should definite the Class of what is inside of JSONResponse["data"].arrayValue, or how Xcode knows it.
If you want to get an data array which is made of Dictionary , here is how I cheated it out.
func getDatas(from json: Any?) -> [Dictionary] {
guard let json = json as? NSDictionary, let entries = json.value(forKeyPath: "data") as? [NSDictionary] else {
return []
}
return entries.flatMap { entry in
guard let dictionary = entry as? NSDictionary else {
return nil
}
return dictionary
}
}
you call it as such
self.arrResponseReceived = getDatas(from: json)
NSDictionary has the method value(forKeyPath:) that is near magic. Calling json.value(forKeyPath: "data") returns an array of dictionaries. Each dictionary is an "entry" object in the json. Next, I map each entry which returns a dictionary.
If this is something more than a quick solution, then I would consider adding SwiftyJSON to your project.
func getDatas(from json: Any?) -> [Dicitonary] {
return JSON(json)["data"].arrayValue.flatMap { entry in
guard let dictionary = entry as? NSDictionary else {
return nil
}
return dictionary
}
}
In my object Dish xxx.Dish, I want to access the Choice class price and name to display but I failed. dish data load from web API and I tested data loaded success full and put the data to the object dish and it return the object list to viewcontroller to load tableview.
Output of printed console
Optional([xxx.Dish, xxx.Dish])
and in the dish class before append optionList?.append(_obj)
xxx.DishOption
Anyone helps me how can I do that .. I am new to swift and is it right way to implement? Please suggest me?
class Dish {
let dishId : String
var optionList : [DishOption]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let dishId = resposne["dishId"] as? String else {
return nil
}
self.dishId = dishId
if let objs = resposne["options"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = DishOption(fromAPIResponse: obj){
optionList?.append(_obj)
}
}
}
}
class DishOption {
let optionId : String
var choiceList : [Choice]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let optionId = resposne["optionId"] as? String else {
return nil
}
self.optionId = optionId
if let objs = resposne["choices"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = Choice(fromAPIResponse: obj){
choiceList?.append(_obj)
}
}
}
}
}
class Choice{
let choiceId : String
let name : String
let price : String
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let choiceId = resposne["choiceId"] as? String ,
let name = resposne["name"] as? String,
let price = resposne["price"] as? String else {
return nil
}
self.choiceId = choiceId
self.name = name
self.price = price
}
}
UPDATE:
var dishMenuList = [Dish]()
guard let objs = json["menu_list"] as? [[String : AnyObject]] else {
return
}
for obj in objs {
if let _obj = Dish(fromAPIResponse: obj){
print(_obj.optionList) //always print nil
if let options = _obj.optionList {
for data in options {
print(data.displayAsButton)
}
}
dishMenuList.append(_obj)
}
}
From what I can see, you are never initializing both the optionList and choiceList arrays. It would be better to initialize them as empty arrays:
class Dish {
let dishId : String
var optionList = [DishOption]()
...
optionList.append(_obj)
This is the reason that you cannot see any options. Since the optionList is still nil, the line optionList?.append(_obj) does not execute.
I'm having a array of CNContact and I sort them with this function:
for contact in self.contacts {
var contactName = contact.organizationName
let key: String = String(contactName.characters.first).uppercaseString
if let arrayForLetter = self.contactDictionary[key] {
self.contactDictionary[key]!.append(contact)
self.contactDictionary.updateValue(arrayForLetter, forKey: key)
} else {
self.contactDictionary.updateValue([contact], forKey: key)
}
}
self.keys = self.contactDictionary.keys.sort()
Where contactDictionary is of type:
var contactDictionary: [String: [CNContact]] = [String: [CNContact]]()
var keys: [String] = []
Now when I see the contactDictionary when it's filled it works except the key always Optional(\"T"\") or some other letter of course. But why is it optional? The key in the forloop is not optional so how does this come?
first property of Collection is of optional type, So you are getting optional probably here contactName.characters.first, if you wrapped it using if let or guard will solved your issue.
if let fc = name.characters.first {
let key = String(fc).uppercaseString
}
Try to do it this way:
for contact in self.contacts {
var contactName = contact.organizationName
if let key = String(contactName.characters.first).uppercaseString {
if let arrayForLetter = self.contactDictionary[key] {
self.contactDictionary[key]!.append(contact)
self.contactDictionary.updateValue(arrayForLetter, forKey: key)
} else {
self.contactDictionary.updateValue([contact], forKey: key)
}
}
}
self.keys = self.contactDictionary.keys.sort()
My app takes some data from this API: https://api.jqestate.ru/v1/properties/country
GitHub link to my project: https://github.com/armansharvel/JQ-Estate.git (download branch "Refreshing")
There are no compiler errors but when I run my app in the simulator Xcode prints in console "Fatal error: Index out of range".
In the ObjectModel.swift I created a class of the object with some data types. One of them is the variable mainPic (URL of picture for TableVeiw that I want to get from the API also). But the problem is not every object in the API contains value of URL of the picture.
So Xcode (when I try to run the app) marks the second line of code block that initialises mainPic variable and the error is: "Thread 7: EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)"
Here is the whole class in code:
import Foundation
class Houses {
// Data Encapsulation
private var _mainPic: String
private var _localityName: String
private var _routeName: String
private var _mkadDistance: String
private var _rentOffer: String
private var _saleOffer: String
// Make a getted
var mainPic: String {
return _mainPic
}
var localityName: String {
return _localityName
}
var routeName: String {
return _routeName
}
var mkadDistance: String {
return _mkadDistance
}
var rentOffer: String {
return _rentOffer
}
var saleOffer: String {
return _saleOffer
}
// Initialization
init(data: JSONDictionary) {
// Main Picture
if let images = data["images"] as? JSONArray,
pic0 = images[0] as? JSONDictionary, // THIS LINE IS WITH ERROR
mainPic = pic0["url"] as? String {
self._mainPic = mainPic
} else {
_mainPic = ""
}
// Locality Name
if let location = data["location"] as? JSONDictionary,
localityName = location["localityName"] as? String {
self._localityName = localityName
} else {
_localityName = ""
}
// Route Name
if let location = data["location"] as? JSONDictionary,
routeName = location["routeName"] as? String {
self._routeName = routeName
} else {
_routeName = ""
}
// MKAD Distance
if let location = data["location"] as? JSONDictionary,
mkadDistance = location["mkadDistance"] as? String {
self._mkadDistance = mkadDistance
} else {
_mkadDistance = ""
}
// Rent Offer
if let rentDict = data["rentOffer"] as? JSONDictionary,
rentOffer = rentDict["price"] as? String {
self._rentOffer = rentOffer
} else {
_rentOffer = ""
}
// Sale Offer
if let saleDict = data["saleOffer"] as? JSONDictionary,
saleOffer = saleDict["price"] as? String {
self._saleOffer = saleOffer
} else {
_saleOffer = ""
}
}
}
Just in case, JSONDictionary and JSONArray are just typealiases:
typealias JSONDictionary = [String : AnyObject]
typealias JSONArray = Array<AnyObject>
Thanks in advance!
images[0] will crash with "Fatal error: Index out of range" if the images array is empty.
Since you're using optional binding, use first instead of [0]:
if let images = data["images"] as? JSONArray,
pic0 = images.first as? JSONDictionary,
mainPic = pic0["url"] as? String {
self._mainPic = mainPic
} else {
_mainPic = ""
}
I am try to implement search bar to my table view. But I am getting this error in one function. Don't know how to solve??
Value of type '[Businessdata]' has no member 'objectAtIndex'
My code
var arrDict = [Businessdata]()
func searchMethod(notification:NSNotification)
{
isSearching = true;
let text:String = notification.userInfo!["text"] as! String;
arrSearch = [];
for(var i=0;i<arrDict.count;i++)
{
if((arrDict.objectAtIndex(i).objectForKey("name")?.lowercaseString?.containsString(text.lowercaseString)) == true)
{
arrSearch.addObject(arrDict.objectAtIndex(i));
}
}
TableViewList.reloadData();
}
Edited :
import UIKit
class Businessdata: NSObject {
var BusinessName: String?
var BusinessEmail: String?
var BusinessLatLng: NSArray?
var Address: String?
var ContactNumber: String?
var WebsiteUrl: String?
var Specialities:Array<String>?
var StoreImages: NSArray?
var Languages:Array<String>?
var PaymentMethod:Array<String>?
var OpenHours: [NSDictionary]?
var Rating: Float?
var Updated_date: String?
var FeaturedBusiness: NSDictionary?
init(json: NSDictionary)
{
self.BusinessName = json["business_name"] as? String
self.BusinessEmail = json["business_email"] as? String
self.BusinessLatLng = json["latlng"] as? NSArray
self.Address = json["location"] as? String
self.ContactNumber = json["phone_no"] as? String
self.WebsiteUrl = json["website_url"] as? String
self.Specialities = json["specialities"] as? Array<String>
self.StoreImages = json["images"] as? NSArray
self.Languages = json["languages"] as? Array<String>
self.PaymentMethod = json["method_payment"] as? Array<String>
self.OpenHours = json["opening_hours"] as? [NSDictionary]
self.Rating = json["__v"] as? Float
self.Updated_date = json["updated_at"] as? String
if((json["featured_business"]) != nil)
{
self.FeaturedBusiness = json["featured_business"] as? NSDictionary
}
}
}
Here i have posted the Bussinessdata class code.Now how to solve for my problem
Help me out!!
There is no objectAtIndex in an array. You need to do something like this:
arrDict[i]
Instead of
arrDict.objectAtIndex(i)
Edit
As we discussed in the comments this is what you need
if((arrDict[i].name.lowercaseString?.containsString(text.lower‌​caseString)) == true)
Try this one:
func searchMethod(notification:NSNotification)
{
isSearching = true;
let text:String = notification.userInfo!["text"] as! String;
arrSearch = [];
for(var i=0;i<arrDict.count;i++)
{
if((arrDict[i].BusinessName.lowercaseString?.containsString(text.lower‌​caseString)) == true)
{
arrSearch.addObject(arrDict[i]);// or arrSearch.append(arrDict[i])
}
}
TableViewList.reloadData();
}
objectAtIndex: belongs to NSArray and objectForKey: belongs to NSDictionary.
Both are not available for the Swift native types.
But there are two fatal issues:
Businessdata is a custom class which does not respond to objectForKey: at all, and there is no property name in the class.
Assuming you are talking about the property BusinessName and the logic is supposed to filter all Businessdata instances whose lowercase string of BusinessName contains the search string you might write
arrSearch = [Businessdata]()
for item in arrDict {
if let businessName = item.BusinessName as? String where businessName.lowercaseString.containsString(text.lowercaseString) {
arrSearch.append(item)
}
}
or swifiter
arrSearch = arrDict.filter({ (item) -> Bool in
if let businessName = item.BusinessName as? String {
return businessName.lowercaseString.containsString(text.lowercaseString)
}
return false
})
And please conform to the naming convention and use always variable names starting with a lowercase letter.