How to create a generic array with multiple models in Swift? - ios

I am trying to create a generic array with different models. I have a parser method like that. But it doesn't work because it returns [Any] and it's not typesafe. I need to access my Movie and CastMember objects after parse method. I will use this array in my tableviewcontroller delegate methods. How can I do that?
static func parseSearchResult(_ data:Dictionary<String, AnyObject>) -> [Any] {
var array = [Any]()
let jsonData = JSON(data)
if let resultData = jsonData["results"].arrayObject {
let result = resultData as! [[String:AnyObject]]
for element in result {
if((element["media_type"]?.isEqual("person"))!){
let person = CastMember(json: element)
array.append(person)
}
else if((element["media_type"]?.isEqual("movie"))!){
let movie = Movie(json: element)
array.append(movie)
}
}
}
return array
}
and these are my structs
struct CastMember{
var id : Int?
var originalName : String?
var castName : String?
var picturePath : String?
init(json: [String:Any]){
originalName = json["name"] as? String
id = json["id"] as? Int
castName = json["character"] as? String
picturePath = "https://image.tmdb.org/t/p/w200/"
picturePath?.append((json["profile_path"] as? String) ?? "")
}
}
struct Movie{
var id : Int?
var title : String?
var imagePath : String?
init(json: [String:Any]){
title = json["title"] as? String
id = json["id"] as? Int
imagePath = "https://image.tmdb.org/t/p/w200/"
imagePath?.append((json["poster_path"] as? String)!)
}
}

Make your Movie and CastMember classes confirm to the protocol Codable.
also you will have to write a struct or class which matches the response data , like it must have an array of results and any other key coming in response.
struct ResponseModel<T> : Codable {
let results : [T]
}
then decode it like this :
let response : ResponseModel = JSONDecoder.decode(T.self)

You should make a protocol
Example:
enum MediaType {
case movie, castMember
}
protocol SearchResult {
var title: String { get }
var mediaType: MediaType { get }
}
struct SearchResultViewModel: SearchResult {
let title: String
let mediaType: MediaType
init(title: String, mediaType: MediaType) {
self.title = title
self.mediaType = mediaType
}
}
Then your parseSearchResult should return an array of [SearchResult] objects that conforms to the protocol, in this case, an array of SearchResultViewModel

Related

Swift: display arrayobject to tableView

im trying to dispaly array object come from api response as [[String: Any]] at table view
and thats my struct
class CategoriesDep: NSObject {
var depName: String
var depImage: String
var subName = [subData]()
init?(dict: [String: JSON]) {
guard let image = dict["main_department_image"]?.imagePath, !image.isEmpty else { return nil }
self.depImage = image
self.depName = (dict["main_department_name"]?.string)!
}
struct subData {
var dep: String
init(dic: [String: Any]) {
self.dep = dic["sub_department_name"] as! String
}
}
}
Please check below code to parse your json
class CategoriesDep: NSObject {
var depName: String
var depImage: String
var subName = [subData]()
init?(dict: [String: Any]) {
guard let image = dict["main_department_image"] as? String, !image.isEmpty else { return nil }
self.depImage = image
self.depName = (dict["main_department_name"] as? String)!
subName = []
for subDict in (dict["sub_depart"] as? [[String:Any]] ?? []){
subName.append(subData(dic: subDict))
}
}
}
struct subData {
var dep: String
var image :String
var id : String
init(dic: [String: Any]) {
self.dep = dic["sub_department_name"] as! String
self.image = dic["sub_department_image"] as! String
self.id = dic["sub_department_id"] as! String
}
}
and if you want to access subdata struct out side of CategoriesDep class then declare structure outside CategoriesDep class
Parse your given json Respoise like
let json = [
[ "sub_depart" : [
[ "sub_department_name" : "hos", "sub_department_id" : "6", "sub_department_image" : "23.jpg"
]
],
"main_department_id" : "2",
"main_department_name" : "main ",
"main_department_image" : "14.jpg"
],
]
var catDepart : [CategoriesDep] = []
for dict in json {
catDepart.append(CategoriesDep(dict: dict)!)
}
print(catDepart[0].subName[0].dep)
You could use Codabel protocol to be more swifty ;) and cleaning up the code.
let jsonString = "[{\"sub_depart\" : [ {\"sub_department_name\" : \"hos\", \"sub_department_id\" : \"6\", \"sub_department_image\" : \"23.jpg\" } ], \"main_department_id\" : \"2\", \"main_department_name\" : \"main \", \"main_department_image\" : \"14.jpg\"}]"
struct CategoriesDep: Codable {
let mainDepartmentName: String
let mainDepartmentImage: String
let mainDepartmentId: String
var subDepart: [SubData] = []
}
struct SubData: Codable {
let subDepartmentName: String
let subDepartmentImage: String
let subDepartmentId: String
}
if let jsonData = jsonString.data(using: .utf8) {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
var departments: [CategoriesDep]? = try? decoder.decode([CategoriesDep].self, from: jsonData)
...
}
Note the decoder.keyDecodingStrategy = .convertFromSnakeCase here which is mapping the underscore (snake_case) API property names to your camelCase ones.
If you need different property names you have to implement CodingKeys enum to map them.
For more detailed information check this link.

Swift: how to convert a [String:Any] array to a specific array

I have this simple Struct:
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Item {
var title:String
var text:String?
var dictionary:[String:Any] {
return [
"title":title,
"text":text,
]
}
}
extension Item : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let title = dictionary["title"] as? String,
let text = dictionary["text"] as? String? else {return nil}
self.init(title: title, text: text)
}
}
When I recive my json, I put it in an array...
if let array = result?.data as? Array<[String:Any]> {...
How can I convert this array into an array of Items? var itemsArray = [Item]()
The two arrays have exactly the same structure
Thanks
Use
struct Item :Decodable {
let title:String
let text:String?
}
//
do {
let root = try JSONDecoder().decode([Item].self, from:jsonData)
print(root)
}
catch {
print(error)
}
Use compactMap, it handles also the nil cases:
itemsArray = array.compactMap{ Item(dictionary: $0) }
However in Swift 4 it's highly recommended to use the Codable protocol

iterating through plist file and storing data in array or Dictionary

I am new in swift programing. I'm trying to learn plist file. I have a plist file which has data of Country, State and city. I want to iterate through the data of plist file. But I can't understand how to create an array or dictionary to store the data for country, state and City. Can you help me with how to manipulate the following data of plist file.
{
Country = (
{
CountryName = India;
State = (
{
City = (
Ahmedabad,
Vadodara,
Surat,
Aanand,
Bharuch
);
StateName = Gujrat;
},
{
City = (
Mumbai,
Pune,
Nagpur,
Nasik,
Thane
);
StateName = Maharastra;
},
{
City = (
Kochi,
Kanpur,
Alleppey,
Thrissur,
Thiruvananthapuram
);
StateName = Kerala;
}
);
},
// swift 3 using structure. Manually parsing of data.
struct ListData {
var countries : [Country]?
init(dict:[String:AnyObject]) {
if let countryDict = dict["Country"] as? [[String:AnyObject]] {
self.countries = parseArray(dictArray: countryDict)
}
}
func parseArray(dictArray:[[String:AnyObject]]) -> [Country] {
var array = [Country]()
for dict in dictArray {
let country = Country(dict: dict)
array.append(country)
}
return array
}
}
struct Country {
var countryName : String?
var states : [State]?
init(dict:[String:AnyObject]) {
countryName = dict["CountryName"] as? String
if let stateDict = dict["State"] as? [[String:AnyObject]] {
states = parseArray(dictArray: stateDict)
}
}
func parseArray(dictArray:[[String:AnyObject]]) -> [State] {
var array = [State]()
for dict in dictArray {
let state = State(dict: dict)
array.append(state)
}
return array
}
}
struct State {
var stateName : String?
var cities : [String]?
init(dict:[String:AnyObject]) {
self.stateName = dict["StateName"] as? String
if let cityDict = dict["City"] as? [AnyObject] {
cities = parseArray(dictArray: cityDict)
}
}
func parseArray(dictArray:[AnyObject]) -> [String] {
var array = [String]()
for dict in dictArray {
array.append(dict as! String)
}
return array
}
}
var listData : ListData? = nil
if let path = Bundle.main.path(forResource: "Property List" , ofType: "plist") {
let rootDict = NSDictionary(contentsOfFile: path) as? [String:AnyObject]
listData = ListData(dict: rootDict!)
}
// Using swift 4 and codable protocol.
struct ListData : Codable {
var countries : [Country]?
enum CodingKeys : String, CodingKey {
case countries = "Country"
}
}
struct Country : Codable {
var countryName : String?
var states : [State]?
enum CodingKeys : String, CodingKey {
case countryName = "CountryName"
case states = "State"
}
}
struct State : Codable {
var stateName : String?
var cities : [String]?
enum CodingKeys : String, CodingKey {
case stateName = "StateName"
case cities = "City"
}
}
var listData : ListData? = nil
if let url = Bundle.main.url(forResource: "Property List", withExtension: "plist") {
if let data = try? Data(contentsOf: url) {
let decoder = PropertyListDecoder()
do {
listData = try decoder.decode(ListData.self, from: data)
} catch (let err) {
print(err.localizedDescription)
}
}
}
You can use Object mapper
https://github.com/Hearst-DD/ObjectMapper
where, install the pod and then
create your class as
class State : Mappable {
var stateName: String?
var city: [String]?
required init?(map: Map) {}
func mapping(map: Map) {
stateName <- map["StateName"]
city <- map["City"]
}
}
class Country : Mappable {
var countryName: String?
var state: [State]?
required init?(map: Map) {}
func mapping(map: Map) {
countryName <- map["CountryName"]
state <- map["State"]
}
}
class CountryData: Mappable {
var countries: [Country]?
required init?(map: Map) {}
func mapping(map: Map) {
countries <- map["Country"]
}
}
get your plist content in a varibale like
var dictRoot: NSDictionary?
var countryItems: [String:AnyObject]?
if let path = Bundle.main.path(forResource: "MyCountryList" , ofType: "plist") {
dictRoot = NSDictionary(contentsOfFile: path)
}
if dictRoot != nil
{
// Your dictionary contains an array of dictionary
// Now pull an Array out of it.
countryItems = dictRoot as? [String:AnyObject]
tableView.reloadData()
}
let countryData = CountryData(JSON: countryItems)
now you can use it as you wish

How to handle array in model class?

How can make model class for this json data
{
total: 41,
totalPages: 4,
valueData: [
{
id: "23",
lastLogin: "0-Jul-2011 11:27:36 AM",
name: "varii"
},
{
id: "24",
lastLogin: "0-Jul-2015 11:27:36 AM",
name: "sarii"
},
{
id: "25",
lastLogin: "0-Jul-2018 11:27:36 AM",
name: "narii"
} ]
}
class OnResponse {
var total: Int?
var totalPages: Int?
init(response: [String: Any]) {
self.total = response["total"]
self.totalPages = response["totalPages"]
}
}
It's not working how can make it ready for work
and how to pass values to controller to model and how to get value from model
Follow the below class structure
class Response {
var total: Int?
var totalPages: Int?
var valueData: [LoginData]?
init(response: [String: Any]) {
self.total = response["total"]
self.totalPages = response["totalPages"]
var items:[LoginData] = ()
for (data in response["valueData"]) {
let login = LoginData(name: data["name"], lastLogin: data["lastLogin"])
items.append(login)
}
self.valueData = items
}
}
class LoginData {
var name: String?
var lastLogin: String?
init(name: String, lastLogin: String) {
self.name = name
self.lastLogin = lastLogin
}
}
you should use "reverseMatches" to retrieve the array, not the "data". May be you can use a third library to convert your json data to a model, such as Unbox, https://github.com/JohnSundell/Unbox.
Model for Your response :
struct ModelName {
var total: Int?
var totalPage: Int?
var reverseMatches: [LoginDetails]?
}
struct LoginDetails {
var id: String?
var lastLogin: String?
var name: String?
}
Parse the api response and assign the values on the appropriate fields. I have made, all the variables are optional.
Assign values like below.
var obj = Model()
obj.total = response["total"] as? Int
obj should be var, because you are going to mutate the struct values. because struct is value based, not reference based
class DataModel{
var total : Int?
var totalPages : Int?
var valueData : [UserModel]?
init(JSON: [String:Any?]){
self = parser.doParse(JSON: JSON)
}
}
class UserModel{
var id : String?
var lastLogin : String?
var name : String?
}
class parser : NSObject{
class func doParse(JSON: [String:Any?])->DataModel{
let dataModel = DataModel()
dataModel.total = JSON["total"] as? Int
dataModel.totalPages = JSON["totalPages"] as? Int
var userInfo : [UserModel] = []
let valueData : [String:String?]? = JSON["valueData"] as? [String:String?]
if let valueData = valueData{
for dataDict : [String:String?] in valueData{
let itemModel = UserModel()
itemModel.id = dataDict["id"] as? String
itemModel.lastLogin = dataDict["lastLogin"] as? String
itemModel.name = dataDict["name"] as? String
userInfo.append(itemModel)
}
dataModel.valueData = userInfo
}
return dataModel
}
}
class LoginData: NSObject {
let total: Int = 0
let totalPages: Int = 0
let valueData: Array<ValueData> = Array<ValueData>()
override init(total: Int!, totalPages: Int, valueData: Array<ValueData>!) {
self.total = total
self.totalPages = totalPages
self.valueData = valueData
}
}
struct ValueData {
let id: int?
let lastLogin: String?
let name: String?
init(id: int!, lastLogin: string, name: string!)
self.id = id ?? 0
self.lastLogin = lastLogin ?? ""
self.name = name ?? ""
}
}
you should use struct instead of class for creating model object...
advantages of struct over class refer
Why Choose Struct Over Class?
class/24232845
use two struct for holding your data one is for your single total count
and other is for last login detail
struct lastLogin {
let totalCount: (total: Int, totalPages: Int)
let valueArray: [lastLoginDetail]
}
struct lastLoginDetail {
let valueData: (id: String, lastLogin: String,name: String)
}
extension lastLoginDetail {
init(json: [String : String]) throws {
let id = json["id"]
let lastLogin = json["lastLogin"]
let name = json["name"]
let value = (id,lastLogin,name)
self.valueData = value as! (id: String, lastLogin: String, name: String)
}
}
extension lastLogin {
init(json: [String : Any]) throws {
let total = (json["total"] as! NSNumber).intValue
let totalPages = (json["totalPages"] as! NSNumber).intValue
let totalCounts = (total,totalPages)
var userInfo : [lastLoginDetail] = []
// Extract and validate valueData
let valueData = json["valueData"] as? NSArray
if let valueData = valueData{
for dataDict in valueData{
let dic : [String : String] = dataDict as! [String : String]
let lastLoginDeatails = try! lastLoginDetail(json: dic)
userInfo.append(lastLoginDeatails)
}
}
self.valueArray = userInfo
self.totalCount = totalCounts
}
}
func HowToUseModelClass(){
let jsonDic = NSDictionary()
// jsonDic // your json value
let dataValue = try! lastLogin(json: (jsonDic as! [String : Any])) // parsing the data
print(dataValue.totalCount.total)
print(dataValue.totalCount.totalPages)
print(dataValue.valueArray[0].valueData.id)
}

How to know if the append function is filling the properties of a NSObject

Swift 2.0 Alamofire 2.0 Xcode 7 IOS 9
I have the next function which calls the API and retrieves the friend list in JSON format, convert the list in dictionary and append it to the Friendship NSObject
func GetFriends(completionHandler: ([FriendShip]?, NSError?) -> ()) {
Alamofire.request(Router.GetFriends(Test().getUserId())).responseJSON { (_, _, result) in
var friends = [FriendShip]()
switch result {
case .Success(let json):
if let responseObject = json as? [String: AnyObject], let hits = responseObject["hits"] as? [[String: AnyObject]] {
for dictionary in hits {
friends.append(FriendShip(dictionary: dictionary))
print(friends)
}
completionHandler(friends, nil)
}
case .Failure(_, let error):
completionHandler(nil, error as NSError)
}
}
the result of print(dictionary) is :
["_id": 546a6ef98e6df97032266, "friend": {
"_id" = 546a4b3e1f8d2c2630dd2;
name = "Daniela";
profileImageUrl = "https://api-static/profile/546a4b3e1f8d2c2630dd2.1.jpg";
statusTxt = "";
}]
["_id": 546a6f988e6df97032266, "friend": {
"_id" = 546a4ba51f8d2c2630dd2;
name = "Mara";
profileImageUrl = "https://api-static/profile/546a4ba51f8d2c2630dd2.1.jpg";
statusTxt = undefined;
}]
["_id": 546a70a18e6df97032266, "friend": {
"_id" = 546a4bd61f8d2c2630dd2;
name = "Alejandro";
profileImageUrl = "https://api-static/profile/546a4bd61f8d2c2630dd2.1.jpg";
statusTxt = "Marty";
}]
["_id": 546a715d8e6df97032266, "friend": {
"_id" = 546a4be01f8d2c2630dd2;
name = "Pedro";
profileImageUrl = "https://api-static/profile/546a4be01f8d2c2630dd1.1.jpg";
}]
classes Friendship and User
class FriendShip: NSObject{
var id: String?
var userId: String?
var user: User?
var friendId: String?
var friend: User?
var date: NSDate?
init(dictionary: [String: AnyObject]){
id = dictionary["id"] as? String
userId = dictionary["userId"] as? String
user = dictionary["user"] as? User
friendId = dictionary["friendId"] as? String
friend = dictionary["friend"] as? User
date = dictionary["date"] as? NSDate
}
override var description : String {
let friendString = friend!.name != nil ? friend!.name! : "nil"
let urlString = friend!.profileImageUrl != nil ? friend!.profileImageUrl! : "nil"
return "Friendship:\nfriend = \(friendString),\nurlString = \(urlString)"
}
}
class User: NSObject{
var id: String?
var name: String?
var statusTxt: String?
var profileImageUrl: String?
init(dictionary: [String: AnyObject]){
id = dictionary["id"] as? String
name = dictionary["name"] as? String
statusTxt = dictionary["statusTxt"] as? String
profileImageUrl = dictionary["profileImageUrl"] as? String
}
override var description : String {
let nameString = name != nil ? name! : "nil"
let profileImageUrlString = profileImageUrl != nil ? profileImageUrl! : "nil"
return "Friendship:\nname = \(nameString),\nprofileImageUrl = \(profileImageUrlString)"
}
}
How can I know/view/print if the friends.append function is working properly and its filling OK all the properties of the Friendship NSObject?
If I understand you correctly you need a way to print / debug the contents of your Friendship class.
NSObject implements the NSObjectProtocol which has a computed property description. That property returns a String that represents the contents of the class. It is the text you see when you print an object. So your Friendship class already inherits this description protocol from NSObject. But in your case it only prints the class name, because that is the default implementation.
So if you want to have a more meaningful description of your class, you have to override the description property:
class Friendship: NSObject {
var name: String?
var age: Int?
override var description : String {
let nameString = name != nil ? name! : "nil"
let ageString = age != nil ? String(age!) : "nil"
return "Friendship:\nname = \(nameString),\nage = \(ageString)"
}
}
I don't know what your actual Friendship class looks like, so I made my own, very simple class that only contains a name and an age.
So now when you have 2 instances of Friendship (one filled and one empty) and you print them, you can see the contents of that objects:
let friendship1 = Friendship()
friendship1.name = "John"
friendship1.age = 34
print(friendship1)
let friendship2 = Friendship()
print(friendship2)
Prints:
Friendship:
name = John,
age = 34
Friendship:
name = nil,
age = nil
UPDATE
In your case the overridden description vars should look like this:
class FriendShip: NSObject{
var id: String?
var userId: String?
var user: User?
var friendId: String?
var friend: User?
var date: NSDate?
init(dictionary: [String: AnyObject]){
id = dictionary["id"] as? String
userId = dictionary["userId"] as? String
user = dictionary["user"] as? User
friendId = dictionary["friendId"] as? String
friend = dictionary["friend"] as? User
date = dictionary["date"] as? NSDate
}
override var description : String {
let friendDescription = friend != nil ? friend!.description : "nil"
let userIdString = userId != nil ? userId! : "nil"
return "Friendship:\nfriend = \(friendDescription),\nuserId = \(userIdString)"
}
}
class User: NSObject{
var id: String?
var name: String?
var statusTxt: String?
var profileImageUrl: String?
init(dictionary: [String: AnyObject]) {
id = dictionary["id"] as? String
name = dictionary["name"] as? String
statusTxt = dictionary["statusTxt"] as? String
profileImageUrl = dictionary["profileImageUrl"] as? String
}
override var description : String {
let nameString = name != nil ? name! : "nil"
let profileImageUrlString = profileImageUrl != nil ? profileImageUrl! : "nil"
return "User:\nname = \(nameString),\nprofileImageUrl = \(profileImageUrlString)"
}
}
You can test it:
// test empty object
let friendship = FriendShip(dictionary: [String: AnyObject]())
print(friendship.description)
// test correct object
let user = User(dictionary: ["name": "John", "profileImageUrl": "http://image.com"])
let friendship2 = FriendShip(dictionary: ["friend": user, "userId": "1"])
print(friendship2.description)
Prints
Friendship:
friend = nil,
userId = nil
Friendship:
friend = User:
name = John,
profileImageUrl = http://image.com,
userId = 1

Resources