I am getting data from a website using rss. I want to set those datas to the variables in struct but I am getting an error in for loop.
struct News {
var title: String
var link: String
}
class HaberTableViewController: UITableViewController, XMLParserDelegate {
var NewsArray:[News] = []
override func viewDidLoad() {
let url = "http://www.ensonhaber.com/rss/ensonhaber.xml"
Alamofire.request(url).responseRSS() { (response) -> Void in
if let feed: RSSFeed = response.result.value {
for item in feed.items {
print(item.title!)
for n in self.NewsArray
{
n.title.append(item.title)
n.link.append(item.link)
}
}
}
}
}
.
.
.
}
I think you are trying to populate the NewArray array with new News instances using values from feed.items. If that's the case then your code should be:
if let feed: RSSFeed = response.result.value {
for item in feed.items {
if let title = item.title, let link = item.link {
let news = News(title: title, link: link)
NewsArray.append(news)
}
}
}
Note that this also deals with item.title (and presumably item.link) being optional.
Please note that variable names should start with lowercase letters so NewsArray should be newsArray.
Related
I am getting JSON data from server by api call in swift application.
So, I want to store that into Realm data base and again need to fetch to show in tableview.
I have no idea about Realm database, After, checked few forums, I got basic idea for creating Object class.
So, I have installed Realm pod file and imported that library to my classes.
My JSON data is
[{
"type": "story",
"story":
{
"author-name": "",
"headline": "Quotes ",
"summary": "Best quotes of Muhammad Ali",
"hero-image": "https://image”
}
},
{
"type": “Trending”,
"story":
{
"author-name": "",
"headline": "Quotes ",
"summary": "Best quotes of Muhammad Ali",
"hero-image": "https://image”
}
},
{
"type": “Technology”,
"story":
{
"author-name": "",
"headline": "Quotes ",
"summary": "Best quotes of Muhammad Ali",
"hero-image": "https://image”
}
},
{
"type": “Top”,
"story":
{
"author-name": "",
"headline": "Quotes ",
"summary": "Best quotes of Muhammad Ali",
"hero-image": "https://image”
}
}
]
And I have each type keyword has different model class saved data from api data to show in Tableview
like
let storyObj = StoryModule()
let trending = StoryModule()
let technology = StoryModule()
let stotopryObj1 = StoryModule()
and I am saving each key value for every type
if abc.type == "story" {
let storyObj = abc.story
storyObj.authorname = storyObj?.authorname
storyObj.heroimage = storyObj?.heroimage
storyObj.headline = storyObj?.headline
storyObj.summary = storyObj?.summary
self.treningStoriesList.append(storyObj)
}
It is same for remaining Trending, Top and Technology objects.
and the Realm module is
import RealmSwift
class DemoInfo: Object {
#objc dynamic var category = ""
let items = List<DemoList>()
}
class DemoList : Object {
#objc dynamic var authorName = ""
#objc dynamic var imageUrl = ""
#objc dynamic var summary = ""
#objc dynamic var headLine = ""
}
And In MainViewController class,
let realmDB = try! Realm()
But, Here I got struck, How to save those storyObj,technology,top, etc module data and fetch.
Can anyone suggest me?
If you want to add a realm object in your db, you must define a primary key for each realm object classes. So, you need to change your JSON file, after you can create your realm objects like this;
DemoObject.swift
import RealmSwift
class DemoObject: Object {
#objc dynamic var id: String = ""
#objc dynamic var type: String = ""
#objc dynamic var subObject: SubObject?
override static func primaryKey() -> String? {
return "id"
}
}
SubObject.swift
import RealmSwift
class SubObject: Object {
#objc dynamic var id: String = ""
#objc dynamic var authorName: String = ""
#objc dynamic var imageUrl: String = ""
#objc dynamic var summary: String = ""
#objc dynamic var headLine: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
Then, you can use these codes to add your db.
let realm = try! Realm()
let demo = DemoObject()
demo.id = "1"
let sub = SubObject()
sub.id = "1"
sub.authorName = "Author Name"
sub.headLine = "Head Line"
sub.summary = "image Url"
demo.subObject = sub
try! realm.write {
realm.add(demo, update: true)
}
What I'm trying to do is retrieve json data(which is in array format) and check to see if my local array already contains the data, if it does move on to the next value in the JSON data until their is a value that the array doesn't contain then append it to the array. This data in the array must be in order. I'm attempting to do this now but get the error:
Type 'ResultsGenrePosters' does not conform to protocol 'Sequence'
This is what it looks like:
public struct ResultsGenrePosters: Decodable {
public let results : [GenrePosters]?
public init?(json: JSON) {
results = "results" <~~ json
}
}
public struct GenrePosters: Decodable {
public let poster : String
public init? (json: JSON) {
guard let poster: String = "poster_path" <~~ json
else {return nil}
self.poster = poster
}
static func updateGenrePoster(genreID: NSNumber, urlExtension: String, completionHandler:#escaping (_ details: [String]) -> Void){
var posterArray: [String] = []
let nm = NetworkManager.sharedManager
nm.getJSONData(type:"genre/\(genreID)", urlExtension: urlExtension, completion: {
data in
if let jsonDictionary = nm.parseJSONData(data)
{
guard let genrePosters = ResultsGenrePosters(json: jsonDictionary)
else {
print("Error initializing object")
return
}
guard let posterString = genrePosters.results?[0].poster
else {
print("No such item")
return
}
for posterString in genrePosters {
if posterArray.contains(posterString){continue
} else { posterArray.append(posterString) } //This is where the error happens
}
}
completionHandler(posterArray)
})
}
}
Alt + click on genrePosters and what does it tell you? It should say its ResultsGenrePosters because thats what the error is saying. Now look at the type of posterArray; its an array of String, not Array ResultsGenrePosters. I think you mean to write for poster in genrePosters and have confused yourself about the types because you wrote for posterString in genrePosters.
Maybe you want to use map to transform genrePosters into a [String] ?
This transforms your posterArray, if it exists into an array containing just the poster names. If it doesn't exist you get an empty array. This only works if poster is String. If its String? you should use flatMap instead.
let posterNames = genrePosters.results?.map { $0.poster } ?? [String]()
I currently have a custom object
public struct GenreData : Decodable {
public let id : NSNumber?
public let name : String
public init?(json: JSON) {
guard let name : String = "name" <~~ json
else {return nil}
self.id = "id" <~~ json
self.name = name
}
}
I have an array of the custom object and am trying to access the 'id' part of the object so I can plug it in another function :
var genreDataArray: [GenreData] = []
var posterStringArray: [String] = []
var posterImageArray: [UIImage] = []
GenreData.updateAllData(urlExtension:"list", completionHandler: { results in
guard let results = results else {
print("There was an error retrieving info")
return
}
self.genreDataArray = results
for _ in self.genreDataArray {
if let movieGenreID = self.genreDataArray[0].id {//This is where the ID is needed to access the posters
print(movieGenreID)
//Update posters based on genreID
GenrePosters.updateGenrePoster(genreID: movieGenreID, urlExtension: "movies", completionHandler: {posters in
//Must iterate through multiple arrays with many containing the same poster strings
for poster in posters {
//Check to see if array already has the current poster string, if it does continue, if not append to array
if self.posterStringArray.contains(poster){
continue
} else {
self.posterStringArray.append(poster)
//Use the poster string to download the corresponding poster
self.networkManager.downloadImage(imageExtension: "\(poster)",
{ (imageData) //imageData = Image data downloaded from web
in
if let image = UIImage(data: imageData as Data){
self.posterImageArray.append(image)
}
})
}
}
})
}
}
DispatchQueue.main.async {
self.genresTableView.reloadData()
}
})
As of right now all its doing is accessing the first array of the JSON data(there are 19 arrays)and appending every string from that first array to the posterStringArray but instead I need it to go through each array and only append the string if doesn't already exist in the posterStringArray.
I am not 100 percent sure I understood your question, but shouldn't you replace:
for _ in self.genreDataArray {
if let movieGenreID = self.genreDataArray[0].id
with
for item in self.genreDataArray {
if let movieGenreID = item.id
update
to only add one item try adding a break after getting the image:
//Use the poster string to download the corresponding poster
self.networkManager.downloadImage(imageExtension: "\(poster)",
{ (imageData) //imageData = Image data downloaded from web
in
if let image = UIImage(data: imageData as Data){
self.posterImageArray.append(image)
}
})
break
I'm trying to work with JSON as MVC model, for this I did:
// Country.swift
import SwiftyJSON
class Country {
var code: String!
var dialCode: Int!
var name: String!
init(json: JSON) {
for i in 0...json["countries"].count - 1 {
if let code = json["countries"][i]["code2"].string, dialCode = json["countries"][i]["dialCode"].string, name = json["countries"][i]["name"].string {
self.code = code
self.dialCode = Int(dialCode)
self.name = name
}
}
}
}
and later in my ViewController I do:
var countries = [Country]()
Alamofire.request(.POST, "\(property.host)\(property.getCountryList)", parameters: parameters, encoding: .JSON).responseJSON { response in
do {
let json = JSON(data: response.data!)
countries.append(Country(json: json))
} catch _ {
}
}
but I have a problem. When I print values in Country.swift file, I get results, but when I print(countries) it returns me [Project.Country] and count returns 1. What is the problem? What I do wrong?
Unless I've misunderstood is this not your desired behaviour?
countries is an array of Project.Country, which swift is representing by printing [Project.Country] (an array containing one instance of your class). There is no issue. If you want to prove that the array contains a Project.Country you should print one of the class' properties: print(countries.first.name)
EDIT: problem is you are passing a JSON array of countries to a single init method, which is just setting the properties of itself for every country and not creating an instance for each. Hence you only have one instance returned
Your problem is you are passing countries array to init method which is called only once you must do it like here
class Country {
var code: String!
var dialCode: Int!
var name: String!
init(json: JSON) {
if let code = json["code2"].string, dialCode = json["dialCode"].string, name = json["name"].string {
self.code = code
self.dialCode = Int(dialCode)
self.name = name
}
}
}
And loop here
Alamofire.request(.POST, "", parameters: nil, encoding: .JSON).responseJSON { response in
if let jsonResponse = response.result.value{
let json = JSON(jsonResponse)
for countriesJSON in json["countries"].arrayValue{
self.countries.append(Country(json: countriesJSON))
}
print(self.countries.count)
}
}
Considering the following model:
class Person: Object {
dynamic var name = ""
let hobbies = Dictionary<String, String>()
}
I'm trying to stock in Realm an object of type [String:String] that I got from an Alamofire request but can't since hobbies has to to be defined through let according to RealmSwift Documentation since it is a List<T>/Dictionary<T,U> kind of type.
let hobbiesToStore: [String:String]
// populate hobbiestoStore
let person = Person()
person.hobbies = hobbiesToStore
I also tried to redefine init() but always ended up with a fatal error or else.
How can I simply copy or initialize a Dictionary in RealSwift?
Am I missing something trivial here?
Dictionary is not supported as property type in Realm.
You'd need to introduce a new class, whose objects describe each a key-value-pair and to-many relationship to that as seen below:
class Person: Object {
dynamic var name = ""
let hobbies = List<Hobby>()
}
class Hobby: Object {
dynamic var name = ""
dynamic var descriptionText = ""
}
For deserialization, you'd need to map your dictionary structure in your JSON to Hobby objects and assign the key and value to the appropriate property.
I am currently emulating this by exposing an ignored Dictionary property on my model, backed by a private, persisted NSData which encapsulates a JSON representation of the dictionary:
class Model: Object {
private dynamic var dictionaryData: NSData?
var dictionary: [String: String] {
get {
guard let dictionaryData = dictionaryData else {
return [String: String]()
}
do {
let dict = try NSJSONSerialization.JSONObjectWithData(dictionaryData, options: []) as? [String: String]
return dict!
} catch {
return [String: String]()
}
}
set {
do {
let data = try NSJSONSerialization.dataWithJSONObject(newValue, options: [])
dictionaryData = data
} catch {
dictionaryData = nil
}
}
}
override static func ignoredProperties() -> [String] {
return ["dictionary"]
}
}
It might not be the most efficient way but it allows me to keep using Unbox to quickly and easily map the incoming JSON data to my local Realm model.
I would save the dictionary as JSON string in Realm. Then retrive the JSON and convert to dictionary. Use below extensions.
extension String{
func dictionaryValue() -> [String: AnyObject]
{
if let data = self.data(using: String.Encoding.utf8) {
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject]
return json!
} catch {
print("Error converting to JSON")
}
}
return NSDictionary() as! [String : AnyObject]
} }
and
extension NSDictionary{
func JsonString() -> String
{
do{
let jsonData: Data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
return String.init(data: jsonData, encoding: .utf8)!
}
catch
{
return "error converting"
}
}
}
UPDATE 2021
Since Realm 10.8.0, it is possible to store a dictionary in a Realm object using the Map type.
Example from the official documentation:
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var currentCity = ""
// Map of city name -> favorite park in that city
let favoriteParksByCity = Map<String, String>()
}
Perhaps a little inefficient, but works for me (example dictionary from Int->String, analogous for your example):
class DictObj: Object {
var dict : [Int:String] {
get {
if _keys.isEmpty {return [:]} // Empty dict = default; change to other if desired
else {
var ret : [Int:String] = [:];
Array(0..<(_keys.count)).map{ ret[_keys[$0].val] = _values[$0].val };
return ret;
}
}
set {
_keys.removeAll()
_values.removeAll()
_keys.appendContentsOf(newValue.keys.map({ IntObj(value: [$0]) }))
_values.appendContentsOf(newValue.values.map({ StringObj(value: [$0]) }))
}
}
var _keys = List<IntObj>();
var _values = List<StringObj>();
override static func ignoredProperties() -> [String] {
return ["dict"];
}
}
Realm can't store a List of Strings/Ints because these aren't objects, so make "fake objects":
class IntObj: Object {
dynamic var val : Int = 0;
}
class StringObj: Object {
dynamic var val : String = "";
}
Inspired by another answer here on stack overflow for storing arrays similarly (post is eluding me currently)...