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)
}
})
}
Related
I am trying to retrieve the value called memberJobfrom a firebase dict. After I retrieved it, the goal is to erase the duplicates, showing the unique values in a TableView.
The problem is that jobsis empty but memberJobactually has the values while looping over.
Maybe someone can help me! :)
import UIKit
import FirebaseDatabase
import Foundation
import FirebaseFirestoreSwift
import CodableFirebase
class ProjectCharacterViewController: UIViewController {
// MARK: - Properties
#IBOutlet weak var specTxt: UITextField!
#IBOutlet weak var difficultyTxt: UITextField!
#IBOutlet weak var budgetTxt: UITextField!
#IBOutlet weak var tableView: UITableView!
var member = [TeamMember]()
var jobs: [String] = []
var uniqueJobs = [MemberJobsStruct]()
var soloJobs: [String] = []
var singleJobs: [String] = []
var test = ["Hallo", "Birne", "Apfel"]
override func viewDidLoad() {
super.viewDidLoad()
getJobs(for: User.current) { (memberJob) in
self.uniqueJobs = memberJob
}
soloJobs = removeDuplicates(array: jobs)
print("SoloJobs :", soloJobs)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
// MARK: - Functions
func gettheJob() {
soloJobs = removeDuplicates(array: jobs)
print("These are the unique Jobs: ", soloJobs)
}
func getJobs(for user: User, completion: #escaping ([MemberJobsStruct]) -> Void) {
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
for case let child as DataSnapshot in snapshot.children {
guard let value = child.value as? [String: Any] else {
return completion ([])
}
let memberJob = value["memberJob"] as! String
self.jobs.append(memberJob)
}
})
}
func removeDuplicates(array: [String]) -> [String] {
var encountered = Set<String>()
var result: [String] = []
for value in array {
if encountered.contains(value) {
// Do not add a duplicate element.
}
else {
// Add value to the set.
encountered.insert(value)
// ... Append the value.
result.append(value)
}
}
return result
}
}
// MARK: - UITableViewDataSource
extension ProjectCharacterViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return soloJobs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let job = jobs[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ProjectCharacterTableViewCell") as! ProjectCharacterTableViewCell
cell.jobLabel.text = soloJobs[indexPath.row]
return cell
}
}
// MARK: - UITableViewDelegate
extension ProjectCharacterViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}
Update: I tried to simply make a new array soloJobsout of jobsbut even this is not working. what is the right approach to do something like this? right now I tried it several ways including this one but nothings working...
func getJobs(for user: User, completion: #escaping ([MemberJobsStruct]) -> Void) {
var jobs: [String] = []
let ref = Database.database().reference().child("team").child(user.uid)
ref.observe(DataEventType.value, with: { snapshot in
for case let child as DataSnapshot in snapshot.children {
guard let value = child.value as? [String: Any] else {
return completion ([])
}
let memberJob = value["memberJob"] as! String
jobs.append(memberJob)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
soloJobs = jobs
}
You're probably just missing the part where you should reload the UITableView after the getJobs method has appended the jobs.
func getJobs(for user: User, completion: #escaping ([MemberJobsStruct]) -> Void) {
//...
ref.observe(DataEventType.value, with: { snapshot in
for case let child as DataSnapshot in snapshot.children {
//...
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
Update: For getting the Unique objects from an array using the extension method from here.
I've got a couple a problem with this code. Here's my code and I don't understand why there is an error line 61 with cell.userID = self.user[indexPath.row].userID it says : Cannot assign value of type String? to type String?.Type. It's probably because in line 36 : if let uid = value["profilepicture.userID"] as? String. userID is in Firebase a child of profile picture but I don't know how to write that inside of value[]. Thanks for your answers.
// TableViewCell.swift
import UIKit
class FriendsTableViewCell: UITableViewCell {
#IBOutlet weak var userImage: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
var userID = String?.self
}
// ViewController.swift
import UIKit
import Firebase
class FriendsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableview: UITableView!
var user = [User]()
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func retrieveUsers() {
let ref = Database.database().reference()
ref.child("users").queryOrderedByKey().observeSingleEvent(of: .value, with: { DataSnapshot in
let users = DataSnapshot.value as! [String: AnyObject]
self.user.removeAll()
for (_, value) in users{
//let uid = Auth.auth().currentUser!.uid
if let uid = value["profilepicture.userID"] as? String{
if uid != Auth.auth().currentUser!.uid {
let userToShow = User()
if let fullName = value["username"] as? String , let imagePath = value["profilepicture.photoURL"] as? String {
userToShow.username = fullName
userToShow.imagePath = imagePath
userToShow.userID = uid
self.user.append(userToShow)
}
}
}
}
})
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableview.dequeueReusableCell(withIdentifier: "FriendsTableViewCell", for: indexPath) as! FriendsTableViewCell
cell.nameLabel.text = self.user[indexPath.row].username
cell.userID = self.user[indexPath.row].userID
cell.userImage.downloadImage(from: self.user[indexPath.row].imagePath!)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return user.count ?? 0
}
}
extension UIImageView{
func downloadImage(from imgURL: String!) {
let url = URLRequest(url: URL(string: imgURL)!)
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil{
print(error)
return
}
DispatchQueue.main.async {
self.image = UIImage(data: data!)
}
}
task.resume()
}
}
Cannot assign value of type String? to type String?.Type.
Change
var userID = String?.self
To
var userID : String?
I'm trying to show data in UITablleView after getting it from http request. I heard that the best and professional way to do it is configure the cell and do the call request in model class then send it to the view controller. So here is what I tried to do but I think it's not going well. I know this is a wrong wy but I wanted to show you what I got based on someone.
UITable View Cell :
#IBOutlet weak var status : UILabel!
#IBOutlet weak var date : UILabel!
#IBOutlet weak var model : UILabel!
func ConfigCell (car : Car){
let urlstr = "http://localhost:8000/api/newuser/check"
let url = URL(string: urlstr)
guard let token = UserDataSingleton.sharedDataContainer.token else { return }
let headers = ["Authorization":"Bearer \(token)"]
var statusCode: Int = 0
request(url! , method: .get, encoding: JSONEncoding.prettyPrinted , headers: headers )
.responseJSON { response in
if let value: AnyObject = response.result.value as AnyObject? {
//Handle the results as JSON
let json = JSON(value)
for (key, subJson) in json["allcar"] {
if let status = subJson["status"].string {
self.status.text = status
if let date = subJson["created_at"].string {
self.date.text = date
if let model = subJson["model"].string {
self.model.text = model
let Status = [
Car(model: model, status: status, date: date)]
Car model
class Car {
private var _mode : String?
private var _status : String?
private var _date : String?
var model : String{
return _model!
}
var status : String {
return _status!
}
var date : String {
return _date!
}
init(model : String , status :String,date : String) {
self._status = status
self._model = model
self._date = date
}
Controller
class testViewController: UIViewController, UITableViewDelegate , UITableViewDataSource {
let data = [Car]()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
tableview.delegate = self
tableview.dataSource = self
print(data.count)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CarCell", for: indexPath) as! CarC
let entry = data[indexPath.row]
cell.date.text = entry.date
cell.model.text = entry.model
cell.status.text = entry.status
return cell
I could do all in the view controller and cell labels in a different model but I'm seeking the "professional" way of doing this
problem with this code
The data array is 0 so there's no data showing in the table
I am trying to create a selection method for users to pick a football team using UITableView.
When the View Controller loads I make an API call and fetch and populated the following arrays in custom structs:
Country [name,id]
Divisions [id, country_id, name]
Teams [id, division_id, name]
The process is:
The user first selects a country > the ID of this country is then used to populate the table with all the divisions from that country > a division is selected which brings up all the teams in that division > a team is selected and the team name and id are passed to a variable to be used elsewhere.
At the moment the table displays all the countries. Once a user selects a country I am using print (self.newCountries[cellCountryId!]) to identifies and print the name of the country and it's id.
How do I now take that data and re-populate the table where Divisions.country_id = (self.newCountries[cellCountryId!])
This is my code:
import UIKit
class PickTeamViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var teamsTableView: UITableView!
var pickedCountryID: Int?
var selectedCellCountryTitle: String?
var cellCountryId: Int?
struct Country {
var name: String?
var countryId: String?
init(_ dictionary: [String : String]) {
self.name = dictionary["name"]
self.countryId = dictionary["id"]
}
}
struct Divisions {
var divisionName: String?
var divisionId: String?
init(_ dictionary: [String : String]) {
self.divisionName = dictionary["name"]
self.divisionId = dictionary["country_id"]
}
}
struct Teams {
var teamName: String?
var newTeamId: String?
init(_ dictionary: [String : String]) {
self.teamName = dictionary["name"]
}
}
struct TeamId {
var newTeamId: String?
init(_ dictionary: [String : String]) {
self.newTeamId = dictionary["id"]
}
}
var newCountries = [Country]()
var newDivisions = [Divisions]()
var newTeams = [Teams]()
var newTeamId = [TeamId]()
override func viewDidAppear(_ animated: Bool) {
let myUrl = URL(string: "http://www.quasisquest.uk/KeepScore/getTeams.php?");
var request = URLRequest(url:myUrl!);
request.httpMethod = "GET";
let task = URLSession.shared.dataTask(with: myUrl!) { (data: Data?, response: URLResponse?, error: Error?) in
DispatchQueue.main.async
{
if error != nil {
print("error=\(error)")
return
}
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any]
print (json)
if let arr = json?["countries"] as? [[String:String]] {
self.newCountries = arr.flatMap { Country($0) }
self.teamsTableView.reloadData()
}
if let arr = json?["divisions"] as? [[String:String]] {
self.newDivisions = arr.flatMap { Divisions($0) }
}
if let arr = json?["teams"] as? [[String:String]] {
self.newTeams = arr.flatMap { Teams($0) }
}
self.teamsTableView.reloadData()
} catch{
print(error)
}
}
}
task.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.newCountries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let country = newCountries[indexPath.row]
let cell = UITableViewCell()
cell.textLabel?.text = country.name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
cellCountryId = indexPath.row
print (self.newCountries[cellCountryId!])
}
override func viewDidLoad() {
super.viewDidLoad()
self.teamsTableView.delegate = self
self.teamsTableView.dataSource = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func backButtonTapped(_ sender: AnyObject) {
self.dismiss(animated: true) {
return
}
}
}
I have an instance where a user picks from a UITable. The selected record has a name and an id associated with it.
At the moment to verify the name and id are being correctly reported I am using
let tempCountryId = (self.newCountries[cellCountryId!])
print (tempCountryId)
Country(name: Optional("England"), countryId: Optional("5"))
I want to be able to store that countryId in a variable so I can repopulate my UITable with data (Football Divisions) that match the countryId '5'
How do I do this?
This is my full script:
import UIKit
class PickTeamViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var teamsTableView: UITableView!
var pickedCountryID: Int?
var selectedCellCountryTitle: String?
var cellCountryId: Int?
struct Country {
var name: String?
var countryId: String?
init(_ dictionary: [String : String]) {
self.name = dictionary["name"]
self.countryId = dictionary["id"]
}
}
struct Divisions {
var divisionName: String?
var divisionId: String?
init(_ dictionary: [String : String]) {
self.divisionName = dictionary["name"]
self.divisionId = dictionary["country_id"]
}
}
struct Teams {
var teamName: String?
var newTeamId: String?
init(_ dictionary: [String : String]) {
self.teamName = dictionary["name"]
}
}
struct TeamId {
var newTeamId: String?
init(_ dictionary: [String : String]) {
self.newTeamId = dictionary["id"]
}
}
var newCountries = [Country]()
var newDivisions = [Divisions]()
var newTeams = [Teams]()
var newTeamId = [TeamId]()
override func viewDidAppear(_ animated: Bool) {
let myUrl = URL(string: "http://www.quasisquest.uk/KeepScore/getTeams.php?");
var request = URLRequest(url:myUrl!);
request.httpMethod = "GET";
let task = URLSession.shared.dataTask(with: myUrl!) { (data: Data?, response: URLResponse?, error: Error?) in
DispatchQueue.main.async
{
if error != nil {
print("error=\(error)")
return
}
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:Any]
print (json)
if let arr = json?["countries"] as? [[String:String]] {
self.newCountries = arr.flatMap { Country($0) }
self.teamsTableView.reloadData()
}
if let arr = json?["divisions"] as? [[String:String]] {
self.newDivisions = arr.flatMap { Divisions($0) }
}
if let arr = json?["teams"] as? [[String:String]] {
self.newTeams = arr.flatMap { Teams($0) }
}
self.teamsTableView.reloadData()
} catch{
print(error)
}
}
}
task.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.newCountries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let country = newCountries[indexPath.row]
let cell = UITableViewCell()
cell.textLabel?.text = country.name
cell.textLabel?.font = UIFont(name: "Avenir", size: 12)
cell.textLabel?.textColor = UIColor.black
cell.backgroundColor = UIColor.white
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
cellCountryId = indexPath.row
// print (self.newCountries[cellCountryId!])
let tempCountryId = (self.newCountries[cellCountryId!])
print (tempCountryId)
}
override func viewDidLoad() {
super.viewDidLoad()
self.teamsTableView.delegate = self
self.teamsTableView.dataSource = self
// Do any additional setup after loading the view.
}
}
As discussed in the comments you should use another view controller to show the details. In didSelectRowAtIndexPath method take out the selected country from newCountries array and pass it to the DetailViewController.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let countryDetailsVC = self.storyboard?.instantiateViewController(withIdentifier: "CountryDetailsViewController") as! DetailViewController
countryDetailsVC.country = selectedCountry
present(countryDetailsVC, animated: true, completion: nil)
}
Now that you have the country Struct you can show its details in the DetailViewController.
Your table is populated from the array newCountries. So, to replace the contents of the table, you would need to replace the contents of newCountries and reload the table.
But that is not a very wise strategy. It would be better to show a different view controller with a different table and a different data array.