I am new to Swift. I know how to get a single piece of data from Firebase, but when I try to get a list of data into an array, I get no error or no data. Please help me. I have been struggled with this for days now.
I want to add data from Firebase into array,
I have created json file with list of categories and imported in firebase.
My JSON file look like this:
{
"Category" : [ {
"categoryId" : "1",
"imageName" : "cat_001.png",
"title" : "CAT"
}, {
"categoryId" : "2",
"imageName" : "dog_001.png",
"title" : "DOG"
}, {
"categoryId" : "3",
"imageName" : "fish_001.png",
"title" : "FISH"
}, {
"categoryId" : "4",
"imageName" : "bird_001.png",
"title" : "BRID"
}]
}
Firebase database looks like
this
Category class looks like this
struct Category {
private(set) public var title: String
private(set) public var imageName: String
init(title: String, imageName: String) {
self.title = title
self.imageName = imageName
}
}
I use custom cell to show my data and here is my custom cell class
class CategoryCell: UITableViewCell {
#IBOutlet weak var categoryImage: UIImageView!
#IBOutlet weak var categoryTitle: UILabel!
func updateViews(category: Category){
categoryImage.image = UIImage(named: category.imageName)
categoryTitle.text = category.title
}
}
And I use DataService class to get data, right now data is hard coded and its working fine.
class DataService{
static let instance = DataService()
// How to add data from firebase in here`?
private let categories = [Category(title: "CAT", imageName: "cat_001"),
Category(title: "DOG", imageName: "dog_001"),
Category(title: "FISH", imageName: "fish_001")]
func getCategories() -> [Category]{
return categories
}
}
and finally here is my ViewController
class CategoriesVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var categoryTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
categoryTable.dataSource = self
categoryTable.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DataService.instance.getCategories().count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as? CategoryCell {
let category = DataService.instance.getCategories()[indexPath.row]
cell.updateViews(category: category)
return cell
}else{
return CategoryCell()
}
}
}
I am going to add more categories in the future.
With hard coded data my app looks like this and i want to achieve same result with data from firebase.
Try it like this, just use an array of Category's for the tableview datasource:
var tableData = [Category]()
then in viewDidLoad, set up a firebase observer to update that array any time there are changes to the Category node in firebase:
ref.child("Category").observe(.value, with: { snapshot in
var newTableData: [Category] = []
for category in snapshot.children {
let dict = category.value as! [String: AnyObject]
let title = dict["title"] as! String
let imageName = dict["imageName"] as! String
let newCategory = Category(title: title,
imageName: imageName)
newTableData.append(newCategory)
}
self.tableData = newTableData
self.tableview.reloadData()
})
Related
I am trying to make a comment section for my open source social media app. I have a table of posts. When you click a post in this table, it takes you to the MainViewController, where you can read the comments on those posts and post your own comment. The Post class is as follows:
import Foundation
class Post {
var id:String
var title: String
var text:String
var createdAt:Date
var comment: [String] = []
init(id: String, title: String,text:String, timestamp:Double, comment: [String] = []) {
self.id = id
self.title = title
self.text = text
self.createdAt = Date(timeIntervalSince1970: timestamp / 1000)
}
static func parse(_ key:String, data:[String:Any]) -> Post? {
if let title = data["text"] as? String,
let text = data["title"] as? String,
let timestamp = data["timestamp"] as? Double {
return Post(id: key, title: title, text: text, timestamp:timestamp, comment: [])
}
return nil
}
}
And the MainTextViewController has the following code:
import Foundation
import UIKit
import Firebase
class MainTextView: UIViewController, UITextViewDelegate{
#IBOutlet weak var titleText: UILabel!
#IBOutlet weak var mainText: UILabel!
#IBOutlet weak var commentsTable: UITableView!
#IBOutlet weak var commentPlaceHolder: UILabel!
#IBOutlet weak var newCommentLabel: UITextView!
weak var delegate:NewPostVCDelegate?
#IBAction func reply(_ sender: UIButton) {
// Firebase code here
let postRef = Database.database().reference().child("posts").childByAutoId()
let postObject = [
"comment": newCommentLabel.text,
"timestamp": [".sv": "timestamp"]
] as [String : Any]
postRef.setValue(postObject, withCompletionBlock: { error, ref in
if error == nil {
self.delegate!.didUploadPost(withID: ref.key!)
self.dismiss(animated: true, completion: nil)
} else {
// Handle error
}
})
newCommentLabel.text = String()
commentPlaceHolder.isHidden = false
}
var post: Post?
// MARK: - View Controller LifeCycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setMain()
}
override func viewDidLoad() {
super.viewDidLoad()
print(delegate!)
commentsTable.dataSource = post?.comment as? UITableViewDataSource
newCommentLabel.delegate = self
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return post!.comment.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let label = UILabel(frame: CGRect(x:0, y:0, width:200, height:50))
cell.addSubview(label)
return cell
}
// UITableViewDelegate Functions
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 50
}
private func setMain() {
guard let post = self.post else {
return
}
titleText.text = post.text
mainText.text = post.title
}
func textViewDidChange(_ commentView: UITextView) {
commentPlaceHolder.isHidden = !newCommentLabel.text.isEmpty
}
}
Here is my database structure:
{
"posts" : {
"-LhaxLOSY3UI7tDUrCA_" : {
"text" : "Nelson",
"timestamp" : 1560800488059,
"title" : "Hey\t"
},
"-LhaxbnjgDP7tdb7Eq_4" : {
"text" : "Lol",
"timestamp" : 1560800558514,
"title" : "How’s it going"
},
"comment" : {
"comment" : "Howdy"
}
}
}
I want that when I make a comment, it appends the comments array for the post with a new comment. Then everyone can see the comments in the commentsTable as a collection of strings from the array with the oldest one on top.
At the moment, creating a new comment makes a new post in Firebase with just a comment as a single string and a timestamp. How would you fix this issue so that the post button appends the table and the commentsTable shows the strings from the array? Let me know if you need me to post more details. Thank you for the help.
Without going overboard with a bunch of code, conceptually, if you want to have a series of posts, and then each post can additionally have comments, here's one option for a structure.
posts
post_id_0
text: "some text"
timestamp: "20190601"
title: "post title"
post_uid: "uid_0"
comments
comment_id_0
belongs_to_post: "post_id_0"
comment: "a comment about the post"
timestamp: "20190601"
comment_uid: "uid_49"
comment_id_1
belongs_to_post: "post_id_0"
comment: "check out that first post!"
timestamp: "20190602"
comment_uid: "uid_102"
users
uid_0
name: "Leroy"
then attach observers to posts and comments. When a new post is posted, you'll be notified of that post and can add it to your tableView datasource and refresh the tableView.
When a new comment is added, you'll be notified of that comment and add it to the comments dataSource and reload the comments tableView. To add a new post:
let thisPostRef = your_firebase.posts.childByAutoId()
thisPostRef.setValue(your_post_data)
and to add a comment
let postKey = the_post.key
let commentRef = your_firebase.comments.childByAutoId()
commentRef.setValue(your_comment_data)
and your_comment_data would include a child node 'belongs_to_post: postKey'
You can also watch for comments on certain posts, made by certain users or even query for comments by date or in a date range.
Code wise, both posts and comments nodes are created with .childByAutoId - it's best practice to disassociate node keys from the data they contain, unless it going to be static data, like a uid.
If you want to add a bit more flexibility, you could keep a child node within each posts of it's related comments as well.
posts
post_id_0
text: "some text"
timestamp: "20190601"
title: "post title"
post_uid: "uid_0"
comments:
comment_id_0: true
comment_id_1: true
but that depends on what kinds of queries you want to run.
Note: I separate the comments structure from the posts the posts node as denormalizing your data is very beneficial when running queries.
I have a Swift struct Reflection like this:
struct Reflection {
let title: String
let body: String
let author: String
let favorite: Bool
let creationDate: Date
let id: UUID
}
extension Reflection {
var plistRepresentation: [String: AnyObject] {
return [
"title": title as AnyObject,
"body": body as AnyObject,
"author": author as AnyObject,
"favorite": favorite as AnyObject,
"creationDate": creationDate as AnyObject,
"id": id as AnyObject
]
}
init(plist: [String: AnyObject]) {
title = plist["title"] as! String
body = plist["body"] as! String
author = plist["author"] as! String
favorite = plist["favorite"] as! Bool
creationDate = plist["creationDate"] as! Date
id = plist["id"] as! UUID
}
}
class StorageController {
fileprivate let documentsDirectoryURL = FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask)
.first!
fileprivate var notesFileURL: URL {
return documentsDirectoryURL
.appendingPathComponent("Notes")
.appendingPathExtension("plist")
}
func save(_ notes: [Reflection]) {
let notesPlist = notes.map { $0.plistRepresentation } as NSArray
notesPlist.write(to: notesFileURL, atomically: true)
}
func fetchNotes() -> [Reflection] {
guard let notePlists = NSArray(contentsOf: notesFileURL) as? [[String: AnyObject]] else {
return []
}
return notePlists.map(Reflection.init(plist:))
}
}
class StateController {
fileprivate let storageController: StorageController
fileprivate(set) var notes: [Reflection]
init(storageController: StorageController) {
self.storageController = storageController
self.notes = storageController.fetchNotes()
}
func add(_ note: Reflection) {
notes.append(note)
storageController.save(notes)
}
func update(_ note: Reflection) {
for (index, storedNote) in notes.enumerated() {
guard storedNote.id == note.id else {
continue
}
notes[index] = note
storageController.save(notes)
break
}
}
}
Instantiating a Reflection like this in viewWillAppear crashes my app:
import UIKit
class NotesViewController: UIViewController {
var stateController: StateController!
fileprivate var dataSource: FeedDataSource!
#IBOutlet var tableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let reflection = Reflection(title: "Hello", body: "world", author: "Alex", favorite: true, creationDate: Date(), id: UUID())
//stateController.add(reflection)
dataSource = FeedDataSource(notes: stateController.notes)
tableView.dataSource = dataSource
tableView.reloadData()
}
class FeedDataSource: NSObject {
var notes: [Reflection]!
init(notes: [Reflection]) {
self.notes = notes
}
}
extension FeedDataSource: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reflectionCell", for: indexPath) as! ReflectionCell
let index = indexPath.row
let note = notes[index]
cell.model = ReflectionCell.Model(data: note)
return cell
}
}
The cell class:
class ReflectionCell: UITableViewCell {
#IBOutlet weak fileprivate var titleLabel: UILabel!
#IBOutlet weak fileprivate var bodyLabel: UILabel!
#IBOutlet weak fileprivate var authorLabel: UILabel!
#IBOutlet weak fileprivate var bookmarkButton: UIButton!
fileprivate var id: UUID!
var model: Model? {
didSet {
guard let model = model else {
return
}
titleLabel.text = model.title
bodyLabel.text = model.body
authorLabel.text = model.author
bookmarkButton.isSelected = model.favorite
id = model.id
}
}
override func awakeFromNib() {
super.awakeFromNib()
bookmarkButton.setImage(#imageLiteral(resourceName: "Bookmark-Highlighted"), for: .selected)
}
}
extension ReflectionCell {
struct Model {
let title: String
let body: String
let author: String
let favorite: Bool
let id: UUID
init(data: Reflection) {
title = data.title
body = data.body
author = data.author
favorite = data.favorite
id = data.id
}
}
}
I get no console output, just a main thread SIGABRT error. What could be going on?
Like an idiot I was cleaning up my code and commented out the line registering the nib for the reuse identifier. However, I do think it would help if Xcode could print out a useful error message for such a mistake.
I have a custom cell class say UserDetailsCustomCell. I have 4 labels in this class Name, Gender, DOB, Address. Whats the best place to set the values in the labels. Shall I pass all these values to cell class and let cell class display these values or shall I set these values in ViewController cellForRowAtIndexPath method.
What is the best way and why?
Thanks
I would recommend you to keep your logic related to cell's data population inside your cell. Create a method which populates your cell data and call it from your viewcontroller.
i think it's best to encapsulate your view logics inside your view. so other people don't get involved with view's logic when they want to use them.
for example
final class UserDetailsCustomCell: UICollectionViewCell {
var user: User! {
didSet {
guard user != nil else { return }
nameLabel.text = user.name
genderLabel.text = user.gender
addressLabel.text = user.address
dateOfBirthLabel.text = user.dateOfBirth
}
}
private #IBOutlet weak var nameLabel: UILabel!
private #IBOutlet weak var genderLabel: UILabel!
private #IBOutlet weak var dateOfBirthLabel: UILabel!
private #IBOutlet weak var addressLabel: UILabel!
}
here when someone wants to use UserDetailsCustomCell they dont need to know how to set labels or any other view logics. they just set user and the cell do the rest for them
cell.user = someUser
first create objectModel to aggregate the data:
struct UserDetailsObjectModel {
var username: String
var gender: String
var dob: String
var address: String
}
create dataSource var in UserDetailsCustomCell:
class UserDetailsCustomCell: UICollectionViewCell {
var dataSource: UserDetailsObjectModel? {
willSet {
if let userDetails = newValue {
// do what ever u want with the user data
}
}
}
}
finally pass the userDetailsObject AKA dataSource in cellForItem method
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "userDetailsCell", for: indexPath) as! UserDetailsCustomCell
let dataSource = UserDetailsObjectModel("insert the data in each param!")
cell.dataSouece = dataSource
return cell
}
This is one of the way for passing JSON values in CustomCell
Sample JSON Format
{
"studentListJSON" : [
{
"name" : "mcdonal",
"age" : "27",
"gender" : "male"
},
{
"name" : "jack",
"age" : "29",
"gender" : "male"
},
{
"name" : "rose",
"age" : "24",
"gender" : "female"
},
]
}
Singleton declaration
class Singleton: NSObject {
static let sharedInstance = Singleton()
var studentListJSON = NSArray()
}
JSON Parsing
func JSONParsing()
{
let url = URL(string: urlString) // YOUR API URL
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! NSDictionary
Singleton.sharedInstance.studentListJSON = parsedData.value(forKey: "studentListJSON") as! NSArray
// RELOAD CollectionView in main thread, if Singleton.sharedInstance.studentListJSON.count > 0
} catch let error as NSError {
print(error)
}
}
}.resume()
}
Parse JSON Array to Singleton.sharedInstance.studentListJSON. Then reloadData() with numberOfItems as Singleton.sharedInstance.studentListJSON.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "userDetailsCell", for: indexPath) as! UserDetailsCustomCell
let datasDict = Singleton.sharedInstance.studentListJSON[indexPath.item] as! NSDictionary
cell.name.text = datasDict.value(forKey: "name") as! String
cell.age.text = datasDict.value(forKey: "age") as! String
cell.gender.text = datasDict.value(forKey: "gender") as! String
return cell
}
I'm working on MVVM architecture in Swift with UITableView. For this, I have created sample table view.
Can any one please suggest whether I am going correct or any other improvements need to do?
The following are the classes for this architecture.
ViewController - Contains UITableView and its delegate and datasource methods.
class ViewController: UIViewController {
let PRODUCT_CELL_IDENTIFIER = "ProductCellIdentifier"
#IBOutlet weak var productTableView: UITableView!
var productViewModel: ProductViewModel = ProductViewModel()
}
//UITableView Delegate Methods
extension ViewController {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productViewModel.numberOfRowsInSection()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PRODUCT_CELL_IDENTIFIER) as! ProductTableViewCell
let product = productViewModel.productsArray[indexPath.row]
cell.productName.text = product.name
cell.productQuantity.text = "\(product.quantity)"
return cell
}
}
ProductViewModel: - This is ViewModel class.
class ProductViewModel: NSObject {
var productsArray = Array<Product>()
override init() {
let product1 = Product(name: "Prodcut1", image_url: "", quantity: 2)
let product2 = Product(name: "Prodcut2", image_url: "", quantity: 3)
productsArray.append(product1)
productsArray.append(product2)
}
func numberOfRowsInSection() -> Int {
return productsArray.count
}
}
Product - This is the model class
class Product: NSObject {
var name: String
var image_url: String
var quantity: Int
init(name: String, image_url: String, quantity: Int) {
self.name = name
self.image_url = image_url
self.quantity = quantity
}
}
ProductTableViewCell - This is UITableViewCell class
class ProductTableViewCell: UITableViewCell {
#IBOutlet weak var productQuantity: UILabel!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var productImageView: UIImageView!
}
You are doing good job, but you can even improve you product model with adding following function to get array of direct models. It is very useful when you have create array from web Api response.
class Product : NSObject
{
var imgUrl : String!
var name : String!
var quantity : Int!
init(dictionary: [String:Any])
{
imgUrl = dictionary["img_url"] as? String
name = dictionary["name"] as? String
quantity = dictionary["quantity"] as? Int
}
init(name: String, image_url: String, quantity: Int)
{
self.name = name
self.imgUrl = image_url
self.quantity = quantity
}
public class func modelsFromArray(array:[[String:Any]]) -> [Product]
{
var models:[Product] = []
for item in array
{
models.append(Product.init(dictionary:item))
}
return models
}
}
With Usage Like
let product1 = Product(name: "Prodcut1", image_url: "", quantity: 2) //Normal Case
let productList:[[String:Any]] =
[
["name":"Jaydeep","img_url":"xyz","quantity":1],
["name":"Jaydeep","img_url":"xyz","quantity":2],
["name":"Jaydeep","img_url":"xyz","quantity":3],
["name":"Jaydeep","img_url":"xyz","quantity":4],
["name":"Jaydeep","img_url":"xyz","quantity":5],
["name":"Jaydeep","img_url":"xyz","quantity":6]
]
//Assign Direct Dictionary to Get Array Of Models
/* Very useful when productList is dictionary from server response*/
let productArray:[Product] = Product.modelsFromArray(array: productList)
And Also your Cell Class is Improved By
class ProductTableViewCell: UITableViewCell {
#IBOutlet weak var productQuantity: UILabel!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var productImageView: UIImageView!
func setProductData(product:Product)
{
self.productName.text = product.name
self.productQuantity.text = "\(product.quantity)"
}
}
Usage:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PRODUCT_CELL_IDENTIFIER) as! ProductTableViewCell
let product = productViewModel.productsArray[indexPath.row]
cell.setProductData(product:product)
return cell
}
MVVM in iOS can be easily implemented without using third party dependencies. For data binding, we can use a simple combination of Closure and didSet to avoid third-party dependencies.
public final class Observable<Value> {
private var closure: ((Value) -> ())?
public var value: Value {
didSet { closure?(value) }
}
public init(_ value: Value) {
self.value = value
}
public func observe(_ closure: #escaping (Value) -> Void) {
self.closure = closure
closure(value)
}
}
An example of data binding from ViewController:
final class ExampleViewController: UIViewController {
private func bind(to viewModel: ViewModel) {
viewModel.items.observe(on: self) { [weak self] items in
self?.tableViewController?.items = items
// self?.tableViewController?.items = viewModel.items.value // This would be Momory leak. You can access viewModel only with self?.viewModel
}
// Or in one line:
viewModel.items.observe(on: self) { [weak self] in self?.tableViewController?.items = $0 }
}
override func viewDidLoad() {
super.viewDidLoad()
bind(to: viewModel)
viewModel.viewDidLoad()
}
}
protocol ViewModelInput {
func viewDidLoad()
}
protocol ViewModelOutput {
var items: Observable<[ItemViewModel]> { get }
}
protocol ViewModel: ViewModelInput, ViewModelOutput {}
final class DefaultViewModel: ViewModel {
let items: Observable<[ItemViewModel]> = Observable([])
// Implmentation details...
}
Later it can be replaced with SwiftUI and Combine (when a minimum iOS version of your app is 13)
In this article, there is a more detailed description of MVVM
https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
I am building an app whereby you enter ingredients and you return a bunch of recipes based on your input. I'm making the calls to the API using alamofire and these seem to be successful. The problem I'm having is the 6 results in my test call are repeating 1 recipe 6 times rather than returning all the results in separate cells. This is the API call code:
import Alamofire
class RecipeAp: NSObject{
var concoctions = [RecipeDetails]()
func provideRecipeDetailsForName(name: String, completed:#escaping ([RecipeDetails]) -> Void) {
let urlSearchString = URL_FULL + "onion" + "soup"
Alamofire.request(urlSearchString).responseJSON(completionHandler: { response in
let details = RecipeDetails()
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
if let matches = dict["matches"] as? [[String: Any]] {
for ingredient in matches {
if let name = ingredient["ingredients"] as? [String] {
details.ingredients = name
self.concoctions.append(details)
}
}
for recipeName in matches {
if let name = recipeName["recipeName"] as? String {
details.recipeTitle = name
print("the recipe name = \(name.debugDescription)")
self.concoctions.append(details)
}
}
}
completed(self.concoctions)
}
})
}
}
This is my model:
class RecipeDetails: NSObject {
var recipeID: String?
var recipeImageURL: String?
var recipeTitle: String?
var recipeSourceURL: String?
var recipePublisher: String?
var ingredients: [String]?
}
This is my customCell setup
import UIKit
class RecipeListCustomCell: UITableViewCell {
#IBOutlet weak var recipeTitle: UILabel!
#IBOutlet weak var recipeUrl: UILabel!
var recipe: RecipeDetails? {
didSet {
updateView()
}
}
func updateView() {
recipeTitle.text = recipe?.recipeTitle
recipeUrl.text = recipe?.recipeSourceURL
}
}
And finally this is my viewController
import UIKit
class MainVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var recipe = RecipeAp()
var results = [RecipeDetails]()
override func viewDidLoad() {
super.viewDidLoad()
loadRecipes()
}
func loadRecipes() {
recipe.provideRecipeDetailsForName(name: "onion" + "soup") { (response) in
self.results = response
self.tableView.reloadData()
}
}
}
extension MainVC: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return results.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"RecipeListCustomCell", for: indexPath) as! RecipeListCustomCell
let recipe = results[indexPath.row]
cell.recipe = recipe
return cell
}
}
Not sure how to display all the recipes separately in each cell. I have also attached some screen shots on what I am getting back from the API and the display in the simulator.
You create only one instance of RecipeDetails for each response. So, you add exactly the same reference into your self.concoctions repeatedly.
You may need to write something like this:
func provideRecipeDetailsForName(name: String, completed: #escaping ([RecipeDetails]) -> Void) {
let urlSearchString = URL_FULL + "onion" + "soup"
Alamofire.request(urlSearchString).responseJSON(completionHandler: { response in
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
if let matches = dict["matches"] as? [[String: Any]] {
for match in matches {
//### Create a new instance for each iteration.
let details = RecipeDetails()
if let ingredients = match["ingredients"] as? [String] {
details.ingredients = ingredients
}
if let recipeName = match["recipeName"] as? String {
details.recipeTitle = recipeName
print("the recipe name = \(recipeName.debugDescription)")
}
//### Add the instance once in the iteration
self.concoctions.append(details)
}
}
completed(self.concoctions)
}
})
}