The problem in the given code is, that the Firebase Data is not loaded into the Arrays before they get returned. Since I am new to swift I am really satisfied with my data model set up to load multiple sections into the tableview and don't want to change everything. The only problem is as I said, that the Firebase data collectors need some kind of a completion handler, but I don't really know how to apply them and I have no idea how to change the following code as well...
This is the class which defines an object:
class Court {
var AnzahlToreKoerbe: String?
var Breitengrad: String?
var Groesse: String?
var Hochgeladen: String?
var Laengengrad: String?
var Stadt: String?
var Stadtteil: String?
var Strasse: String?
var Untergrund: String?
var Upload_Zeitpunkt: Int?
var Platzart: String?
init(AnzahlToreKoerbeString: String, BreitengradString: String, GroesseString: String, HochgeladenString: String, LaengengradString: String, StadtString: String, StadtteilString: String, StrasseString: String, UntergrundString: String, UploadTime: Int, PlatzartString: String) {
AnzahlToreKoerbe = AnzahlToreKoerbeString
Breitengrad = BreitengradString
Groesse = GroesseString
Hochgeladen = HochgeladenString
Laengengrad = LaengengradString
Stadt = StadtString
Stadtteil = StadtteilString
Strasse = StrasseString
Untergrund = UntergrundString
Upload_Zeitpunkt = UploadTime
Platzart = PlatzartString
}
}
This is the class which collects the objects and load them into multiple arrays, which are then called with the getCOurts function in the tableViewController:
class Platzart
{
var Courts: [Court]
var name: String
init(name: String, Courttypes: [Court]) {
Courts = Courttypes
self.name = name
}
class func getCourts() -> [Platzart]
{
return [self.AllSoccer(), self.AllBasketball(), self.Test(), self.Test2()]
}
This is an example private class function which loads the data:
private class func AllSoccer() -> Platzart {
var allSoccer = [Court]()
let databaseref = FIRDatabase.database().reference()
databaseref.child("Court").child("Fußball").observe(.childAdded, with: { (snapshot) in
if let Courtdictionary = snapshot.value as? [String : Any] {
let city = Courtdictionary["Stadt"] as? String
let district = Courtdictionary["Stadtteil"] as? String
let street = Courtdictionary["Strasse"] as? String
let surface = Courtdictionary["Untergrund"] as? String
let latitude = Courtdictionary["Breitengrad"] as? String
let longitude = Courtdictionary["Laengengrad"] as? String
let size = Courtdictionary["Groesse"] as? String
let Userupload = Courtdictionary["Hochgeladen"] as? String
let timestamp = Courtdictionary["Upload_Zeitpunkt"] as? Int
let numberofHoops = Courtdictionary["AnzahlToreKoerbe"] as? String
let courttype = Courtdictionary["Platzart"] as? String
allSoccer.append(Court(AnzahlToreKoerbeString: numberofHoops!, BreitengradString: latitude!, GroesseString: size!, HochgeladenString: Userupload!, LaengengradString: longitude!, StadtString: city!, StadtteilString: district!, StrasseString: street!, UntergrundString: surface!, UploadTime: timestamp!, PlatzartString: courttype!))
print(allSoccer)
}
})
return Platzart(name: "Fußballplatz", Courttypes: allSoccer)
}
The data is then loaded into the tableview:
class CourtlistViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
lazy var Courtarrays: [Platzart] = {
return Platzart.getCourts()
}()
#IBOutlet weak var CourtlisttableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
CourtlisttableView.delegate = self
CourtlisttableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
CourtlisttableView.reloadData()
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let Courttype = Courtarrays[section]
return Courttype.name
}
func numberOfSections(in tableView: UITableView) -> Int {
return Courtarrays.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Courtarrays[section].Courts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = CourtlisttableView.dequeueReusableCell(withIdentifier: "Court Cell", for: indexPath) as! CourtCell
let Platzarray = Courtarrays[indexPath.section]
let Sportplatz = Platzarray.Courts[indexPath.row]
cell.updateUI(Sportplatz)
return cell
}
}
This procedure works well with data which I append to an array manually. For the Firebase Sections (AllSoccer() and AllBasketball()) only the headers are loaded and displayed.
Related
I am using a tableview to show a Project's title including the image.
I'm using FirebaseStorage and FirebaseDatabase.
The Problem is, that when I have only one protect, I get "Fatal error: Index out of range", as soon as I click on the Title.
When I have more than one Project you can see what happens in the video.
Maybe someone can help me, since something isn't right with the index handling. :)
import UIKit
import Kingfisher
import Foundation
import FirebaseStorage
import FirebaseDatabase
class HomeViewController: UIViewController {
// MARK: - Properties
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var addProject: UIButton!
var posts = [Post]()
var textToBeSent: String = ""
override func viewDidLoad() {
super.viewDidLoad()
UserService.posts(for: User.current) { (posts) in
self.posts = posts
self.tableView.reloadData()
}
Utilities.addShadowtoButton(addProject)
}
func configureTableView() {
// remove separators for empty cells
tableView.tableFooterView = UIView()
// remove separators from cells
tableView.separatorStyle = .none
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toDetails" {
let destVC = segue.destination as! ShowProjectDetailsViewController
destVC.post = sender as? Post
}
}
}
extension Collection where Indices.Iterator.Element == Index {
public subscript(safe index: Index) -> Iterator.Element? {
return (startIndex <= index && index < endIndex) ? self[index] : nil
}
}
// MARK: - UITableViewDataSource
extension HomeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let post = posts[indexPath.row]
performSegue(withIdentifier: "toDetails", sender: post)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func numberOfSections(in tableView: UITableView) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = posts[indexPath.section]
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "PostImageCell") as! PostImageCell
let imageURL = URL(string: post.imageURL)
cell.postImageView.kf.setImage(with: imageURL)
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "PostSubCell") as! PostSubCell
cell.projectName.text = post.projectTitle
return cell
default:
fatalError("Error: unexpected indexPath.")
}
}
}
// MARK: - UITableViewDelegate
extension HomeViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.row {
case 0:
let post = posts[indexPath.section]
return post.imageHeight
case 1:
return PostSubCell.height
default:
fatalError()
}
}
}
import Foundation
import FirebaseAuth.FIRUser
import FirebaseDatabase
import FirebaseUI
import FirebaseAuth
struct UserService {
static func posts(for user: User, completion: #escaping ([Post]) -> Void) {
let ref = Database.database().reference().child("posts").child(user.uid)
ref.observe(DataEventType.value, with: { (snapshot) in
guard let snapshot = snapshot.children.allObjects as? [DataSnapshot] else {
return completion([])
}
let posts = snapshot.reversed().compactMap(Post.init)
completion(posts)
})
}
}
import Foundation
import UIKit
import FirebaseDatabase.FIRDataSnapshot
class Post {
// Next let's add properties to store all the additional information we need. Add the following to your post class.
var key: String?
let imageURL: String
let imageHeight: CGFloat
let creationDate: Date
let imageName: String
let projectTitle: String
let projectLocation: String
let projectDescription: String
let projectBeginn: String
let projectEnd: String
// You'll get some compiler errors for not having any initializers or default values for certain properties. Let's go ahead and fix that:
init(imageURL: String, imageName: String, imageHeight: CGFloat, projectTitle: String, projectLocation: String, projectDescription: String, projectBeginn: String, projectEnd: String) {
self.imageURL = imageURL
self.imageName = imageName
self.imageHeight = imageHeight
self.creationDate = Date()
self.projectTitle = projectTitle
self.projectLocation = projectLocation
self.projectDescription = projectDescription
self.projectBeginn = projectBeginn
self.projectEnd = projectEnd
}
var dictValue: [String : Any] {
let createdAgo = creationDate.timeIntervalSince1970
return ["image_url" : imageURL,
"image_name" : imageName,
"image_height" : imageHeight,
"created_at" : createdAgo,
"projectTitle" : projectTitle,
"projectLocation" : projectLocation,
"projectDescription" : projectDescription,
"projectBeginn" : projectBeginn,
"projectEnd": projectEnd ]
}
init?(snapshot: DataSnapshot) {
guard let dict = snapshot.value as? [String : Any],
let imageURL = dict["image_url"] as? String,
let imageName = dict["image_name"] as? String,
let imageHeight = dict["image_height"] as? CGFloat,
let createdAgo = dict["created_at"] as? TimeInterval,
let projectTitle = dict["projectTitle"] as? String,
let projectLocation = dict["projectLocation"] as? String,
let projectDescription = dict["projectDescription"] as? String,
let projectBeginn = dict["projectBeginn"] as? String,
let projectEnd = dict["projectEnd"] as? String
else { return nil }
self.key = snapshot.key
self.imageURL = imageURL
self.imageName = imageName
self.imageHeight = imageHeight
self.creationDate = Date(timeIntervalSince1970: createdAgo)
self.projectTitle = projectTitle
self.projectLocation = projectLocation
self.projectDescription = projectDescription
self.projectBeginn = projectBeginn
self.projectEnd = projectEnd
}
}
Thanks for your help!
You created your numberOfSection by [Post]. And also you assigning performSegue on click of indexPath.row. So it's throws an error, you've to use indexPath.section instead of indexPath.row in didSelectItem() method
e.g.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let post = posts[indexPath.section] // Use here section instead of row
performSegue(withIdentifier: "toDetails", sender: post)
}
I tried to retrieving data from Firebase database to tableview in Xcode
but I just got one element even if I have a lot of element in the database.
I followed a tutorial, I put return sonsList.count to numberOfRowsInSection as suppose but nothing happen.
Here is my code:
import UIKit
import Firebase
import FirebaseDatabase
class sons {
let name : String!
//let place : String!
init(title_String : String!){
self.name = title_String
// self.place = place_String
}
}
class sonsTableViewController: UITableViewController {
var ref:DatabaseReference!
//var sons = [String]()
var newSon: String = ""
let cellId = "cellId"
var refHandel : uint!
var sonsList = [sons]()
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
ref.child("name").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { snapshot in
let value = snapshot.value as? NSDictionary
let name = value!["name"] as! String
self.sonsList.append(sons(title_String : name))
self.tableView.reloadData()
})
//fetchName()
}
func fetchName() {
}
#IBAction func cancel(segue:UIStoryboardSegue) {
}
#IBAction func done(segue:UIStoryboardSegue) {
var sonDetailVC = segue.source as! addSonViewController
newSon = sonDetailVC.name
// sons.append(newSon)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sonsList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let label = cell?.viewWithTag(1) as! UILabel
label.text = sonsList[indexPath.row].name
return cell!
}
}
You have issues in your Database query.
You append only one value in sonsList.
ref = Database.database().reference()
ref.child("name").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { snapshot in
//Parse snapshot value correctly it is array or not.
if let dicValue = snapshot.value as? [String : Any] {
for (key,value) in dicValue {
let name = value["name"] as? String
self.sonsList.append(sons(title_String : name))
}
self.tableView.reloadData()
}
})
Please refer this link for Get data in firebase Database.
https://firebase.google.com/docs/database/ios/read-and-write
extension of Firebase Database Structure
After uploaded the image, name and phone to the Firebase Database and Firebase Storage.
I am having trouble with retrieving the image from the Firebase to the Table View.
This is the Table View Class :
#IBOutlet var tableViewHongKong: UITableView!
var restaurantList = [RestaurantModel]()
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference().child("restaurants")
ref?.observe(FIRDataEventType.value, with: {(snapshot) in
if snapshot.childrenCount>0
{
self.restaurantList.removeAll()
for restaurants in snapshot.children.allObjects as![FIRDataSnapshot]
{
let restaurantObject = restaurants.value as? [String: AnyObject]
let restaurantName = restaurantObject?["name"]
let restaurantPhone = restaurantObject?["phone"]
let restaurant = RestaurantModel(name: name as! String?, phone: phone as! String?)
self.restaurantList.append(restaurant)
}
}
self.tableViewHongKong.reloadData()
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return restaurantList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewControllerTableViewCell
let restaurant: RestaurantModel
restaurant = restaurantList[indexPath.row]
cell.nameLabel.text = restaurant.name
return cell
}
}
This is the Table View Cell Class :
#IBOutlet var myImage: UIImageView!
#IBOutlet var nameLabel: UILabel!
This is the Restaurant Model Class:
var name: String?
var phone: String?
init(name:String?, phone:String?) {
self.name = name;
self.phone = phone
}
As the image uploaded in another class, Restaurant.
var imageURL = ""
func addRestaurant()
{
ref = FIRDatabase.database().reference().child("restaurants")
let key = ref?.childByAutoId().key
let name = addName.text
let phone = addPhone.text
ref?.child(key!).setValue(["name": name, "phone": phone, "image": imageURL])
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
let image = info[UIImagePickerControllerOriginalImage] as? UIImage
addImage.image = image
var data = Data()
data = UIImagePNGRepresentation(image!)!
let uniqueName = NSUUID().uuidString
let imageRef = FIRStorage.storage().reference().child("restaurantImage").child("\(uniqueName)")
imageRef.put(data, metadata: nil).observe(.success){(snapshot) in
self.imageURL = (snapshot.metadata?.downloadURL()?.absoluteString)!
}
self.dismiss(animated: true, completion: nil)
}
For more detail, may visit Firebase Database Structure
Thank you very much ! ^.^
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 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.