Firebase ChildAdded doesn't populate tableView if no new items exist - ios

In the code below, I query for all new posts using the posted date and childAdded, but it seems that when there isn't a new item, the table view doesn't show any items but the firebase block still runs? Please help. I did add number of rows in section. I don't know why stack overflow won't show.
FIRDatabase.database().reference().child("posts").child(uid!).queryOrdered(byChild: "postedDate").observe(.childAdded, with: {(snapshot) in
if (snapshot.hasChildren()) {
print("Has children")
if let dictionary = snapshot.value as? [String: AnyObject] {
let imageCount = dictionary["Images"]?.count
// Post not saved
if (imageCount == nil) {
print("image count is nil")
}
// better catch
else if (imageCount! < 3) {
print("not all 3 were posted")
}
// Saved post
else {
// Adding to details array
self.keyArray.append(snapshot.key)
// Inside a for loop to get every Image
for index in 1 ..< dictionary["Images"]!.count + 1 {
// Check = image1, image2 ...
let check = "image" + String(index)
// Put into the imageArray dictionary
self.imageArray[check] = dictionary["Images"]?[check] as! String?
}
// Handles adding the key and appending the image
self.imageArray2[snapshot.key] = self.imageArray
self.imageArray3.append(self.imageArray2)
// No images but actual values
// Creating makeEasy variables
var valuesArray = [String: String]()
// Adding values to specific array
let name = dictionary["Itemname"] as? String?
// I have all items
if (name != nil) {
// Add values to valueArray
valuesArray["name"] = name!
// Add the Key: UUID
self.details[snapshot.key] = valuesArray
// Then put in the actual array
self.detailsArr.append(self.details)
}
// I DONT have all items
else {
print("Not present!!")
}
// Removing
self.imageArray.removeAll()
self.imageArray2.removeAll()
valuesArray.removeAll()
self.details.removeAll()
}
}
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
else {
print("Has NO children")
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.imageArray3.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "viewTableCell", for: indexPath) as! viewTableViewCell
// get the key first and CHECK IF IT EXISTS ******
let key = keyArray[indexPath.row]
cell.name.text = self.detailsArr[indexPath.row][key]!["name"]
// cache the pic
if let pic = self.imageArray3[indexPath.row][key]?["image1"] {
cell.itemImage.load(pic)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the key and the index
self.tappedIndex = indexPath.row
self.currentKey = keyArray[indexPath.row]
self.performSegue(withIdentifier: "mystify", sender: self)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 400
}

Related

I added a Model version of my .xcdatamodeld and now nothing is working right

Just recently I tried adding a new attribute to my CoreData entity, and from what I've read I'm supposed to create a new Model Version of my .xcdatamodeld through Editor>Add Model Version. However, since I did that none of the data I'm trying to save actually saves anymore, furthermore I am told a new .xcdatamodeld file is supposed to be added to my project and that hasn't happened either.
Here is all my code related to me CoreData from saving to loading:
Saving:
#IBAction func SaveContact(_ sender: Any) {
let present = ContactS(context: managedObjectContext)
if (FN?.text != "" || LN?.text != "" || Email?.text != "" || Phone?.text != "" || Lease?.text != ""){
present.name = FN?.text
present.email = Email?.text
present.coments = Comments?.text
present.phone = Phone?.text
let imgData = Picture.image?.jpegData(compressionQuality: 1)
present.image = imgData
do {
try self.managedObjectContext.save()
}catch{
print("an error has occurred")
}else{
// this part is not important as it only prompts the user to fill out all text fields
}
}
Loading:
var contact = [ContactS]()
func loadData(){
let contactRequest:NSFetchRequest<ContactS> = ContactS.fetchRequest()
do {
contact = try managedObjectContext.fetch(contactRequest)
self.TableView.reloadData()
}catch{
print("Error!")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.TableView = tableView
return contact.count
}
func numberOfSections(in tableView: UITableView) -> Int {
self.TableView = tableView
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
self.TableView = tableView
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! TableClass
let contacts = contact[indexPath.row]
cell.Name.text = contacts.name
cell.Picture.image = UIImage(data: contacts.image!, scale: 1)
return cell
}

Index out of range when i use to celll( Swift)

people, I have this issue when I try back image from different cell
(Thread 1: Fatal error: Index out of range)
what I'm doing here ?
I'm trying to build an Instagram clone and in my home view controller that what should posts show up. I make navigation with a table view and that table view has 2 cell with the different identifier. cell number 1 it's a header that brings data from users table to my username label and profile image. and cell number 2 its for posts its should bring post data like image and caption. I use firebase database.
my code :
import UIKit
import FirebaseAuth
import FirebaseDatabase
class HomeViewController: UIViewController ,UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
var posts = [Post]()
var users = [UserD]()
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
loadposts()
userDetal()
// var post = Post(captiontxt: "test", photoUrlString: "urll")
// print(post.caption)
// print(post.photoUrl)
}
func loadposts() {
Database.database().reference().child("posts").observe(.childAdded){ (snapshot: DataSnapshot)in
print(Thread.isMainThread)
if let dict = snapshot.value as? [String: Any]{
let captiontxt = dict["caption"] as! String
let photoUrlString = dict["photoUrl"] as! String
let post = Post(captiontxt: captiontxt, photoUrlString: photoUrlString)
self.posts.append(post)
print(self.posts)
self.tableview.reloadData()
}
}
}
func userDetal() {
Database.database().reference().child("users").observe(.childAdded){ (snapshot: DataSnapshot)in
print(Thread.isMainThread)
if let dict = snapshot.value as? [String: Any]{
let usernametxt = dict["username"] as! String
let profileImageUrlString = dict["profileImageUrl"] as! String
let user = UserD(usernametxt: usernametxt, profileImageUrlString: profileImageUrlString)
self.users.append(user)
print(self.users)
self.tableview.reloadData()
}
}
}
#IBAction func logout(_ sender: Any) {
do {
try Auth.auth().signOut()
}catch let logoutErrorr{
print(logoutErrorr)
}
let storyboard = UIStoryboard(name: "Start", bundle: nil)
let signinVC = storyboard.instantiateViewController(withIdentifier: "SigninViewController")
self.present(signinVC, animated: true, completion: nil)
}
}
extension HomeViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableview.dequeueReusableCell(withIdentifier: "imagecell", for: indexPath) as! PostCellTableViewCell
cell.postimage.image = nil
cell.tag += 1
let tag = cell.tag
cell.captionLabel.text = posts[indexPath.row].caption
let photoUrl = posts[indexPath.row].photoUrl
getImage(url: photoUrl) { photo in
if photo != nil {
if cell.tag == tag {
DispatchQueue.main.async {
cell.postimage.image = photo
}
}
}
}
return cell
} else if indexPath.row == 1 {
let cell = tableview.dequeueReusableCell(withIdentifier: "postcell", for: indexPath) as! HeaderTableViewCell
cell.userimage.image = nil
cell.tag += 1
let tag = cell.tag
cell.usernamelabel.text = users[indexPath.row].username
//Error showing here????????????????????????????????????
let profileImageUrl = users[indexPath.row].profileImageUrl
getImage(url: profileImageUrl) { photo in
if photo != nil {
if cell.tag == tag {
DispatchQueue.main.async {
cell.userimage.image = photo
}
}
}
}
return cell
}
return UITableViewCell()
}
func getImage(url: String, completion: #escaping (UIImage?) -> ()) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
if error == nil {
completion(UIImage(data: data!))
} else {
completion(nil)
}
}.resume()
}
}
try this one.
cell.tag = indexpath.row
What is the content of users array ?
Are you sure you want to define as many sections as users or as many rows ?
In this case use
func numberOfRows(in tableView: NSTableView) -> Int {
return users.count
}
As explained, you need to rewrite completely cellForRowAt
It should look like this :
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if row < users.count {
let user = users[row]
if let cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CellID"), owner: self) {
(cellView as! NSTableCellView).textField?.stringValue = user.name
// do the same for all the fields you need to set
return cellView
} else {
return nil
}
}
return nil
}
thanx, my friend, I found a good way to contain my cell. for post cell, i just use cellForRowAt and but the post data. for header cell i use viewForHeaderInSection
and but my user data with heightForHeaderInSection. to make the high for a view

Displaying an array of data from plist in tableview

I'm working on displaying data from a plist in multiple "drill down" tableViews. The displayed data is only going to be read-only and I don't want it to necessarily be based on web data, and there will only be a maximum of about 100 data points so I'm fairly happy with plist instead of JSON.
Anyway, the question... I've got an array of dictionary items that I'm managing to display fairly well. I started with the GitHub relating to the following question on stack overflow (I modified it a little bit but thats not important).
Load data from a plist to two TableViews
https://github.com/DonMag/SWPListData
What I need help with is displaying an array of items ("friends" in this example) under each person. Code below will hopefully explain.
I've created an empty array in the model
struct EmployeeDetails {
let functionary: String
let imageFace: String
let phone: String
//This is the new array I've added and am having trouble displaying
let friends: [String]
init(dictionary: [String: Any]) {
self.functionary = (dictionary["Functionary"] as? String) ?? ""
self.imageFace = (dictionary["ImageFace"] as? String) ?? ""
self.phone = (dictionary["Phone"] as? String) ?? ""
//I've initialised it here.
self.friends = (dictionary["Friends"] as? [String]) ?? [""]
Now in the viewController displaying the data. I don't have any problems at all displaying the correct data for the "functionary", "imageFace" and "phone" - but I just can't seem to display "friends" as an array in its own section. I'm pretty sure the main problem is in numberOfRows and cellForRow:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if let theEmployee = newPage {
return theEmployee.details.count
}
return 0
}
else if section == 1 {
return 1
}
else if section == 2 {
if let theEmployee = newPage {
return theEmployee.details.count
}
return 0
}
else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) // as! TableViewCell2
if indexPath.section == 0 {
cell.textLabel?.text = "A"
if let theEmployee = newPage {
cell.textLabel?.text = theEmployee.details[indexPath.row].functionary
cell.detailTextLabel?.text = theEmployee.details[indexPath.row].phone + " (" + theEmployee.details[indexPath.row].imageFace + ")"
}
}
else if indexPath.section == 1 {
cell.textLabel?.text = "Test"
}
else if indexPath.section == 2 {
cell.textLabel?.text = ????
}
return cell
}
I thought it would work writing the following in numberOfRows:
else if section == 2 {
if let theEmployee = newPage {
return theEmployee.details.friends.count
}
return 0
}
But I get the error:
value of type '[EmployeeDetails]' has no member 'friends'.
What do I need to do to get that array?
Note: The array is not empty.
Any suggestions/help would be much appreciated!
Problem is that you are accessing the friends property on details whereas it is a property on the structs stored inside details so you would have to access it through indexing something like this
theEmployee.details[yourIndex].friends.count
Update:
//First you need to make changes inside the .plist file, please go there and add Friends Array inside just like Details
Now this will require you to change your Employee struct code
struct Employee {
let position: String
let name: String
let details: [EmployeeDetails]
let friends: [String]
init(dictionary: [String: Any]) {
self.position = (dictionary["Position"] as? String) ?? ""
self.name = (dictionary["Name"] as? String) ?? ""
let t = (dictionary["Details"] as? [Any]) ?? []
self.details = t.map({EmployeeDetails(dictionary: $0 as! [String : Any])})
self.friends = (dictionary["Friends"] as? [String]) ?? []
}
}
Next step would be to add this in the table view as follows
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let theEmployee = newPage {
if section == 0 {
return theEmployee.details.count
}else if section == 1 {
return theEmployee.friends.count
}
}
return 0
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Subordinates"
}else if section == 1 {
return "Friends"
}
return ""
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) // as! TableViewCell2
cell.textLabel?.text = "A"
if let theEmployee = newPage {
if indexPath.section == 0 {
cell.textLabel?.text = theEmployee.details[indexPath.row].functionary
cell.detailTextLabel?.text = theEmployee.details[indexPath.row].phone + " (" + theEmployee.details[indexPath.row].imageFace + ")"
}else if indexPath.section == 1 {
cell.textLabel?.text = theEmployee.friends[indexPath.row]
}
}
return cell
}
}

refresh tableView after deleting cell

I have a tableView with data populated from Firebase, when I click a delete button the data is removed from Firebase but it remains on my app and it doesn't remove the data from the tableView until I close the app and reopen it. Here is how I set up the delete function:
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
}
}
})
self.TableView.reloadData()
}
Here are the delegate and datasource:
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
posts.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndex = indexPath
self.didExpandCell()
if isExpanded && self.selectedIndex == indexPath{
print(indexPath)
} else{
}}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isExpanded && self.selectedIndex == indexPath{
return 300
}
return 126
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath) as! ProfileTableViewCell
//Configure the cell
let post = self.posts[indexPath.row] as! [String: AnyObject]
cell.Title.text = post["title"] as? String
cell.Author.text = post["Author"] as? String
cell.ISBN10.text = post["ISBN10"] as? String
return cell
}
I attempted to add a tableview.reloaddata at the end of the function but that doesn't help. What am I doing wrong?
Remove your object from posts array and then reload your tableView in main queue one you remove your object from firebase.
Check below code:
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
} else {
//Here remove your object from table array
self.posts.remove(at: indexPath?.row)
//Reload your tableview in main queue
DispatchQueue.main.async{
self.TableView.reloadData()
}
}
}
})
}
Didn't tested it so let me know if you still have issue with above code.
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
return // use return here or use if else
}
// no error has occurred,hence move on to remove the post from the array
self.posts.remove(at: indexPath.row)
} })
DispatchQueue.main.async {
self.TableView.reloadData()
}
}
Delete the removed object from your array also. Your table view gets populated using posts array, not from Firebase object itself. when you click delete button the data is removed from Firebase but it remains on your app, as you have not deleted that object from your array i.e. posts thats why it doesn't remove the data from the tableView until you close the app and reopen it.
You need the remove the deleted object from your posts array and reload that rows to get the effect.
self.posts.remove(at: indexPath.row)
and then reload that specific row itself.
self.tableView.beginUpdates
tableView.reloadRows(at: [indexPath], with: .top)
self.tableView.endUpdates;
In your case
FIRDatabase.database().reference().child("books").child(self.key!).
removeValue { error in
if error != nil {
print("error \(error)")
} else{
self.posts.remove(at: indexPath.row)
self.tableView.beginUpdates
tableView.reloadRows(at: [indexPath], with: .top)
self.tableView.endUpdates;
}
}
})
Hope it helps. Happy Coding!!

Execute code first! Swift

guys I am getting data from Foursquare APi and here is my code below.
But I am getting a nil error at cellForRowAtIndexPath that venueItems is nil
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Table View
self.tableView = UITableView()
// Location Manager Stuff
self.locationManager = CLLocationManager()
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
self.locationManager.delegate = self
let status = CLLocationManager.authorizationStatus()
if status == .notDetermined {
self.locationManager.requestWhenInUseAuthorization()
} else if status == CLAuthorizationStatus.authorizedWhenInUse
|| status == CLAuthorizationStatus.authorizedAlways {
self.locationManager.startUpdatingLocation()
} else {
showNoPermissionsAlert()
}
exploreVenues()
}
// Func's
func exploreVenues() {
guard let location = self.locationManager.location else {
return
}
var parameters = [Parameter.query: "Pubs"]
parameters += location.parameters()
let task = self.session.venues.explore(parameters) {
(result) -> Void in
if self.venueItems != nil {
return
}
if !Thread.isMainThread {
fatalError("!!!")
}
if let response = result.response {
if let groups = response["groups"] as? [[String: AnyObject]] {
var venues = [[String: AnyObject]]()
for group in groups {
if let items = group["items"] as? [[String: AnyObject]] {
venues += items
}
}
self.venueItems = venues
}
self.tableView.reloadData()
} else if let error = result.error, !result.isCancelled() {
self.showErrorAlert(error)
}
}
task.start()
}
// Table View Data source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let venueItems = self.venueItems {
return venueItems.count
}
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! VenueTableViewCell
// This is where the error occurs
let item = self.venueItems![(indexPath as NSIndexPath).row] as JSONParameters!
self.configureCellWithItem(cell, item: item!)
return cell
}
func configureCellWithItem(_ cell: VenueTableViewCell, item: JSONParameters) {
if let venueInfo = item["venue"] as? JSONParameters {
cell.nameLabel.text = venueInfo["name"] as? String
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = cell as! VenueTableViewCell
let tips = self.venueItems![(indexPath as NSIndexPath).row]["tips"] as? [JSONParameters]
guard let tip = tips?.first, let user = tip["user"] as? JSONParameters,
let photo = user["photo"] as? JSONParameters else {
return
}
let URL = photoURLFromJSONObject(photo)
if let imageData = session.cachedImageDataForURL(URL) {
cell.venueImageView.image = UIImage(data: imageData)
} else {
cell.venueImageView.image = nil
session.downloadImageAtURL(URL) { (imageData, error) -> Void in
let cell = tableView.cellForRow(at: indexPath) as? VenueTableViewCell
if let cell = cell, let imageData = imageData {
let image = UIImage(data: imageData)
cell.venueImageView.image = image
}
}
}
}
}
I am quite new to programming personally I think that the venueItems is nil because the cellForRowAtIndexPath is being executed first. If this is the error how can I fix it so the code in cellForRowAtIndexpath runs after my venueItems has a value.. or any other more efficient Way?
Your numberOfRowsInSection returns 10 when self.venueItems is nil. self.venueItems appears to be nil until your network request finishes so the table view, having been told it has 10 rows to display asks for a cell for each row. You then attempt to force unwrap an optional property (self.venueItems!) and crash.
It looks like your self.venueItems is an optional for good reason, don't discard that information with a force unwrap (!). You could either return 0 rows when this property is nil or initialize it to a non-optional empty array which you could then always ask for its count.
In general with this sort of problem you don't want to focus on preventing cellForRowAtIndexPath from being called but rather plan for it to be called at any point and return a reasonable result (like reporting that the table has 0 rows) when your background tasks haven't finished yet.

Resources