How to access values of custom objects in an array? - ios

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

Related

iOS Swift iterate over an Array of Dictionary Items from Firestore

I am trying to iterate over the following dictionary:
Dictionary in Firebase
This is my code:
Global.sharedInstance.db.collection("usuarios").getDocuments { (snapshot, error) in
if error != nil {
print("error de lectura usuarios...")
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let txtIdentificador = data["identificador"] as? String ?? ""
let txtBio = data["bio"] as? String ?? ""
let txtNombre = data["nombre_usuario"] as? String ?? ""
let txtFotoPerfil = data["foto_perfil"] as? String ?? ""
var arrFotos = data["fotos"] as? [String: [String:String]]
}
}
}
}
I am able to retrieve the first few lines, like the id, the biography, name, etc.
But when I try to access the array of dictionary I have no idea.
This is the main idea:
I have a set of users, which I iterate over with the first loop 'for document in documents...", then each user has a set of photos. I want to iterate over the 3 photos, and in each iteration I want to retrieve the fields, so I can create a object called Image and associate the user with the 'hasUpload(Image)'.
I would like to know how to iterate over X photos an in each iteration retrieve the fields.
Something like this:
var arrFotos = data["fotos"] as? [String: [String:String]]
for foto in arrFotos {
for (key,value) in foto {
}
}
I get the error: For-in loop requires '[String : [String : String]]?' to conform to 'Sequence'; did you mean to unwrap optional?
A similar StackOverflow case can be found here and this is how they resolved it:
You can either do this, where x is the index and token is the element:
for (x, token) in transcriptCandidate.enumerated() {
}
Or this if you don't need the index:
for token in transcriptCandidate {
}

Saving array to Core Data

I've created two arrays (imgUrl and imgTitle). I want to save these array values in Core Data. I tried like below. However, it is not successful.
//Mark:- Method to save data in local data base(CoreData)
func saveDataInLocal(imageUrl: [String], imageTitle: [String]){
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
let contactEntity = NSEntityDescription.entity(forEntityName: "Photos", in: context)
let newContact = NSManagedObject(entity: contactEntity!, insertInto: context)
for eachValue in imageTitle{
newContact.setValue(eachValue, forKey: "imgTitle")
}
for eachValue in imageUrl{
newContact.setValue(eachValue, forKey: "imgUrl")
}
do {
try context.save()
fetchData()
} catch {
print("Failed saving")
}
}
XcmodelID is shown in image.
In these two arrays one is image title and another one image URL.
Fetching I'm doing like below.
//Mark:- Method to fetch data from local database(CoreData)
func fetchData(){
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Photos")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
imgTitleNew.append(data.value(forKey: "imgTitle") as! String)
imgUrlNew.append(data.value(forKey: "imgUrl") as! String)
}
} catch {
print("Failed")
}
DispatchQueue.main.async {
self.myCollectionView.reloadData()
}
}
Can somebody suggest how to save the array in Core Data?
Array data displayed below.
var imgUrl = [String]() //contains urls in array
var imgTitle = [String]() //contains titles in array
A simple solution is to save both arrays joined with tab (or other unique) characters and use computed properties for the conversion
Assuming the Core Data properties are declared as
#NSManaged public var imageURL: String
#NSManaged public var imageTitle: String
Add these two computed properties
var imageURLArray : [String] {
get { return imageURL.components(separatedBy: "\t") }
set { imageURL = newValue.joined(separator: "\t") }
}
var imageTitleArray : [String] {
get { return imageTitle.components(separatedBy: "\t") }
set { imageTitle = newValue.joined(separator: "\t") }
}

Require a dictionary to contain certain values (Swift)

I am creating a model for saving user data to a Firestore database and am initializing it with a dictionary. Depending on what fields I want to update, I put those values in the dictionary so that the user model only contains certain fields and will therefore only update those fields in my database. However, I want to somehow require that certain fields are provided in certain use cases.
* Below example is a very simplified version of what I am trying to do *
For example: if I am saving a new user, I want to make sure that I include a name, a profile image, and a description. But if I simply want to update a field, then I don't want to require that all those fields are included
I am 99% certain I am attacking this the wrong way, so any help is appreciated.
My Current User Model:
struct FirestoreUser {
var id: String
var name: String?
var profileImage: String?
var dictionary: [String: Any]
init(dictionary: [String: Any]) {
self.id = dictionary["id"] as! String
self.name = dictionary["name"] as? String
self.profileImage = dictionary["profileImage"] as? String
self.dictionary = dictionary
}
}
// MARK: Firestore functions
extension FirestoreUser {
typealias Handler = ((Error?) -> Void)?
func saveNewFirestoreUser(then handler: Handler = nil) {
// make sure all necessary variables are set
// if they aren't all set, something went wrong
guard let _ = name, let _ = profileImage else { return }
let firestore = Firestore.firestore()
let ref = firestore.collection("users").document(id)
ref.setData(dictionary) { (error) in
if let error = error {
handler?(error)
}
handler?(nil)
}
}
}
Construct struct with optional value and pass nil as the parameter which you don't want to update.
You can use the below extension to convert struct to dictionary later.
struct FirestoreUser: Codable {
var id: String
var name: String?
var profileImage: String?
}
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
Use -
let test = FirestoreUser(id: "01", name: "Abhinav", profileImage: nil)
print(test.dictionary)
Output -
["name": Abhinav, "id": 01]
In realtime database use updateChildValues instead of setValue if you just want to update the child. I believe there's something equivalent in firestore.
Answer for realtime database:
Update
self.ref.child("users/\(user.uid)/username").setValue(username)
Set new data
let key = ref.child("posts").childByAutoId().key
let post = ["uid": userID,
"author": username,
"title": title,
"body": body]
let childUpdates = ["/posts/\(key)": post,
"/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)
Answer for firestore:
Just read the documentation for firestore, use updateData instead of addDocument:
let washingtonRef = db.collection("cities").document("DC")
// Set the "capital" field of the city 'DC'
washingtonRef.updateData([
"capital": true
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
Set/Add new data
// Add a new document with a generated id.
var ref: DocumentReference? = nil
ref = db.collection("cities").addDocument(data: [
"name": "Tokyo",
"country": "Japan"
]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
So rule of thumb is to only pass in the field that you need to update instead of the whole collection/dictionary.

Cannot use mutating member on immutable value: 'n' is a 'let' constant

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.

Type 'customDataObject' does not conform to protocol 'Sequence'

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]()

Resources