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
Related
I'm trying to pass data onto my tableview, from my cloud firestore but it just not able to retrieve the data and post it on the tableview and so far most of of my attempts have failed. since I'm transition from real-time database to Firestore.
I've used multiple resources on stack, restructured my code multiple times and have now come down to this
here is also an image of my collection in Firestore firestore collection
import Foundation
class ProductList {
var id: String?
var name: String?
var dispensaryName: String?
var category: String?,
var brand: String?
var imageUrl: String?
init(id: String?,
name: String?,
dispensaryName: String?,
brand: String?,
category: String?,
imageUrl: String?) {
self.id = id
self.name = name
self.dispensaryName = dispensaryName
self.brand = brand
self.category = category,
self.imageUrl = imageUrl
}
}
import UIKit
class ProductListCell: UITableViewCell {
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var dispensaryName: UILabel!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var categoryStrain: UILabel!
}
import UIKit
import Firebase
import FirebaseFireStore
class ProductListController: UIViewController {
#IBOutlet weak var productListTableView: UITableView!
#IBOutlet weak var menuButton: UIBarButtonItem!
var dbRef: DatabaseReference!
var productSetup: [ProductList] = []
override func viewDidLoad() {
super.viewDidLoad()
productListTableView.dataSource = self
productListTableView.delegate = self
self.productListTableView.rowHeight = UITableView.automaticDimension
self.productListTableView.estimatedRowHeight = 363
menuButton.target = self.revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
dbRef = Database.database().reference().child("products");
//observing the data changes
dbRef.observe(DataEventType.value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.productSetup.removeAll()
//iterating through all the values
for producting in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let productObject = producting.value as? [String: AnyObject]
let id = productObject?["id"]
let name = productObject?["name"]
let dispensaryName = productObject?["dispensaryName"]
let category = productObject?["category"]
let strain = productObject?["strain"]
let imageUrl = productObject?["imageUrl"]
//creating artist object with model and fetched values
let massProducts = ProductList(id: id as! String?,
name: name as! String?,
dispensaryName: dispensaryName as! String?,
category: category as! String?,
strain: strain as! String?,
imageUrl: imageUrl as! String?)
//appending it to list
self.productSetup.append(massProducts)
}
//reloading the tableview
print(self.productSetup)
self.productListTableView.reloadData()
}
})
}
}
extension ProductListController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as!
ProductListCell
let production: ProductList
production = productSetup[indexPath.row]
cell.productName.text = "\(String(describing: production.brand)): \(String(describing: production.name))"
cell.dispensaryName.text = production.dispensaryName
cell.categoryLabel.text = production.category
cell.productImage.text = production.imageUrl
return cell
}
}
I have reformatted the code quickly to make it easier to understand but it could be one of many things;
Check user authenticated with firebase on the device.
Ensure you have setup security settings correctly to allow reads in firebase.
Reformatted Code
ProductListController.swift
import Firebase
class ProductListController: UIViewController {
#IBOutlet weak var productListTableView: UITableView!
#IBOutlet weak var menuButton: UIBarButtonItem!
var productSetup = [ProductList]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
productListTableView.dataSource = self
productListTableView.delegate = self
productListTableView.rowHeight = UITableView.automaticDimension
productListTableView.estimatedRowHeight = 363
menuButton.target = self.revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
fetchProducts { (products) in
self.productSetup = products
self.productListTableView.reloadData()
}
}
func fetchProducts(_ completion: #escaping ([ProductList]) -> Void) {
let ref = Firestore.firestore().collection("products")
ref.addSnapshotListener { (snapshot, error) in
guard error == nil, let snapshot = snapshot, !snapshot.isEmpty else {
return
}
completion(snapshot.documents.compactMap( {ProductList(dictionary: $0.data())} ))
}
}
}
extension ProductListController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productSetup.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProductListCell") as?
ProductListCell else { return UITableViewCell() }
cell.configure(withProduct: productSetup[indexPath.row])
return cell
}
}
ProductListCell.swift
import Firebase
class ProductListCell: UITableViewCell {
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var dispensaryName: UILabel!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var categoryStrain: UILabel!
func configure(withProduct product: ProductList) {
productName.text = "\(String(describing: product.brand)): \(String(describing: product.name))"
dispensaryName.text = product.dispensaryName
categoryLabel.text = product.category
fetchImage(withURL: product.imageUrl ) { (image) in
productImage.image = image
}
}
func fetchImage(withURL url: String, _ completion: #escaping (UIImage) -> Void) {
let ref = Storage.storage().reference(forURL: url)
ref.getData(maxSize: 1 * 1024 * 1024) { (data, error) in
guard error == nil, let imageData = data, let image = UIImage(data: imageData) else {
return
}
completion(image)
}
}
}
ProductList.swift
class ProductList {
var id: String
var name: String
var dispensaryName: String
var category: String
var brand: String
var imageUrl: String
init(id: String, name: String, dispensaryName: String, brand: String, category: String, imageUrl: String) {
self.id = id
self.name = name
self.dispensaryName = dispensaryName
self.brand = brand
self.category = category
self.imageUrl = imageUrl
}
convenience init(dictionary: [String : Any]) {
let id = dictionary["id"] as? String ?? ""
let name = dictionary["name"] as? String ?? ""
let dispensaryName = dictionary["dispensaryName"] as? String ?? ""
let brand = dictionary["brand"] as? String ?? ""
let category = dictionary["category"] as? String ?? ""
let imageUrl = dictionary["imageUrl"] as? String ?? ""
self.init(id: id, name: name, dispensaryName: dispensaryName, brand: brand, category: category, imageUrl: imageUrl)
}
}
I Hope you found this helpful.
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.
In my app i load from database guest's informations.
After checked user profile, i pass all data by using global variables and by sender.
in my tableview i have to display data from variables... the problem is that i cannot put variables to array, so i tried to implement a struct method, but it doesn't works.
//
// SchedaVolontario.swift
// Amesci
//
// Created by Gianluca Caliendo on 06/07/17.
// Copyright © 2017 Amesci. All rights reserved.
//
import UIKit
class SchedaVolontario: UIViewController, UITableViewDataSource, UITableViewDelegate {
var cognome : String?
var sede : String?
var progetto : String?
var datainizioservizio : String?
var datafineservizio : String?
var licenze : String?
var malattie : String?
var CodvolontarioString : String?
struct nomestruct {
var nome : String?
}
struct cognomestruct {
var cognome : String?
}
struct sedestruct {
var sede : String?
}
struct progettostruct {
var progetto : String?
}
struct datainizioserviziostruct {
var datainizioservizio : String?
}
struct datafineserviziostruct {
var datafineservizio : String?
}
struct licenzestruct {
var licenze : String?
}
struct malattiestruct {
var malattie : String?
}
struct codvolontariostruct {
var CodvolontarioString : String?
}
var list = [nomestruct.self, cognomestruct.self, sedestruct.self, progettostruct.self, datainizioserviziostruct.self, datafineserviziostruct.self, licenzestruct.self, malattiestruct.self, codvolontariostruct.self] as [Any]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = list[indexPath.row] as? String
return cell
}
override var prefersStatusBarHidden : Bool {
return true
}
}
It didn't work because you populate a list of types, not values. You should include a reference to your tableView and reload it everytime you populate your list. Try this:
import UIKit
class SchedaVolontario: UIViewController, UITableViewDataSource, UITableViewDelegate {
var nome: String?
var cognome : String?
var sede : String?
var progetto : String?
var datainizioservizio : String?
var datafineservizio : String?
var licenze : String?
var malattie : String?
var codvolontarioString : String?
var guestInfoList: [String] = []
// Connect this outlet to your table view in storyboard
#IBOutlet weak var guestInfoTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
populateGuestInfoList()
guestInfoTableView.reloadData()
}
func populateGuestInfoList() {
if let nome = nome {
guestInfoList.append(nome)
}
if let cognome = cognome {
guestInfoList.append(cognome)
}
if let sede = sede {
guestInfoList.append(sede)
}
if let progetto = progetto {
guestInfoList.append(progetto)
}
if let datainizioservizio = datainizioservizio {
guestInfoList.append(datainizioservizio)
}
if let datafineservizio = datafineservizio {
guestInfoList.append(datafineservizio)
}
if let licenze = licenze {
guestInfoList.append(licenze)
}
if let malattie = malattie {
guestInfoList.append(malattie)
}
if let codvolontarioString = codvolontarioString {
guestInfoList.append(codvolontarioString)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return guestInfoList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = guestInfoList[indexPath.row]
return cell
}
}
Yes, you can put variables into an array. The easiest thing is for them to be a common type. For example:
var list = [nomestruct.nome!, cognomestruct.cognome!, sedestruct.sede!, progettostruct.progretto!, datainizioserviziostruct.datainizioservizio!, datafineserviziostruct.datafineservizio!, licenzestruct.licenze!, malattiestruct.malattie!, codvolontariostruct.codvolontarioString!] as [String]
Judging from the initial declarations (which are not structs), you could also do:
var list = [self.sede!,self.progretto!, self.datainizioservizio!, self.datafineservizio!, self.licenze!, self.malattie!, self.codvolontarioString!] as [String]
This is not very optimal code though, because A) I don't know where or when you are actually setting those structure values and B) I am dereferencing optional strings and if the string is set to nil, your code will crash when it tries to access that string.
I wouldn't use structs for this unless you wanted to add extra data or properties to the structs that you want to reference again later on.
You should edit your question to show when you populate the structures with data and how you do it.
here I had done Code to add objects using struct and passing direct String values to a array of string , Have a look and tell me if you face any problem with it
1) here its written code
//using direct string values
var cognome : String?
var sede : String?
var progetto : String?
cognome = "1"
sede = "2"
progetto = "3"
var listt = [String]()
listt = [cognome!,sede!,progetto!]
//uisng Structs
struct progettostruct {
static var progetto : String?
}
struct datainizioserviziostruct {
static var datainizioservizio : String?
}
progettostruct.progetto = "4"
datainizioserviziostruct.datainizioservizio = "5"
listt.append(progettostruct.progetto!)
listt.append(datainizioserviziostruct.datainizioservizio!)
2) here you can have a look at console output along with my written code
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)
}
})
}
I am currently using a table view to display a menu, in which people can click on the add or subtract buttons to increase/decrease their order.
This table view has approximately 30 items and so you have to scroll down to get to all the menu items. The problem is, when you scroll down, the table view cells above the scroll (that are now hidden) lose the data that they have just contained.
For example, if you have a menu item that you have ordered 2 of an item, that 2 on the label has now turned back to 0. This is very object oriented so I am not sure why this is happening.
My table view class:
#IBOutlet weak var appetizerTableView: UITableView!
var appetizerList = [OrderModel]()
let ref = FIRDatabase.database().reference()
override func viewDidLoad() {
appetizerTableView.delegate = self
appetizerTableView.dataSource = self
ref.child("Broadway").child("AppetizerDishes").observeSingleEventOfType(.Value, withBlock: { snapshot in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
print("WILL: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = OrderModel(postkey: key, postData: postDict)
self.appetizerList.append(post)
}
}
}
self.appetizerTableView.reloadData()
})
}
var data=[OrderModel]()
func addButtonAction(addedList:[String:Float]) {
print("WILLCOHEN:\(addedList)")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return appetizerList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = appetizerList[indexPath.row]
if let cell = appetizerTableView.dequeueReusableCellWithIdentifier("OrderCell") as? OrderCell{
cell.configureCell(post)
cell.delegate = self
return cell
} else {
return OrderCell()
}
}
}
My cell model class:
class OrderModel {
private var _dishName: String!
private var _dishDescription: String!
private var _numberOfOrders: Int!
private var _postKey: String!
private var _dishPrice: Float!
var dishName: String {
return _dishName
}
var dishDescription: String {
return _dishDescription
}
var numberOfOrders: Int {
get {
return _numberOfOrders
}
set (newVal) {
_numberOfOrders = newVal
}
}
var postKey: String {
return _postKey
}
var dishPrice: Float {
return _dishPrice
}
init(dishName: String, dishDescription: String, numberOfOrders: Int) {
self._dishName = dishName
self._dishDescription = dishDescription
}
init(postkey: String, postData: Dictionary<String, AnyObject>) {
self._postKey = postkey
if let dishName = postData["dishName"] as? String {
self._dishName = dishName
}
if let dishDescription = postData["dishDescription"] as? String {
self._dishDescription = dishDescription
}
if let numberOfOrders = postData["anumberOfOrders"] as? Int {
self._numberOfOrders = numberOfOrders
}
if let dishPrice = postData["dishPrice"] as? Float32 {
self._dishPrice = dishPrice
}
}
}
My cell class:
protocol ClassNameDelegate:class {
func addButtonAction(addedList:[String:Float])
}
var addedList: [String:Float] = [:]
class OrderCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
}
var post: OrderModel!
var link: Link!
#IBOutlet weak var dishName: UILabel!
#IBOutlet weak var dishDescriptionAndPrice: UILabel!
#IBOutlet weak var numberOfOrders: UILabel!
#IBOutlet weak var addOrderBtn: UIButton!
#IBOutlet weak var subtractOderBtn: UIButton!
weak var delegate: ClassNameDelegate?
#IBAction func addButtonPressed(sender: AnyObject) {
if post.numberOfOrders == 9 {
numberOfOrders.text = "9"
} else {
if addedList[post.dishName] != nil {
addedList[post.dishName] = post.dishPrice
} else {
addedList["\(post.dishName) \(Int(numberOfOrders.text!)! + 1)"] = post.dishPrice
}
post.numberOfOrders = post.numberOfOrders - 1
numberOfOrders.text = "\(post.numberOfOrders)"
}
if delegate != nil {
delegate?.addButtonAction(addedList)
}
}
#IBAction func subtractButtonPressed(sender: AnyObject) {
if post.numberOfOrders == 0 {
numberOfOrders.text = "0"
} else {
post.numberOfOrders = post.numberOfOrders + 1
numberOfOrders.text = "\(post.numberOfOrders)"
}
}
func getOrders() -> Dictionary<String, Float> {
return addedList
}
func configureCell(post: OrderModel) {
self.post = post
self.dishName.text = post.dishName
self.dishDescriptionAndPrice.text = post.dishDescription
self.numberOfOrders.text = "0"
}
}
I should mention that I am pulling my table view data from Firebase.
Thank you in advance for any help or suggestions, it is very much appreciated.
You are calling configureCell(post:) on every cell, and in the configureCell(post:) function you set the text value of the numbersOfOrders label to "0". You should probably set the text to a value in the OrderModel/post argument.
Also, you should be sure to always call configureCell(post:) (you are not calling it if you have to manually create an OrderCell), so your tableView(tableView:cellForRowAtIndexPath:) function should look like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = appetizerList[indexPath.row]
let cell = appetizerTableView.dequeueReusableCellWithIdentifier("OrderCell") as? OrderCell ?? OrderCell()
cell.configureCell(post)
cell.delegate = self
return cell
}