Search code examples
iosswiftfirebasesearchuisearchcontroller

Firebase Swift - Realtime update does not work when result is filtered by search controller


I am making an iOS app using Swift and Firebase. The view controller retrieves/observes items and stores them in items array. Item cell has + and - buttons that perform an update on the quantity of the item using its indexPath.

If I use search controller to filter items, I get to store search results in filteredItems array. I am trying to update quantity of these filtered items but they only get to update once when I tap + or - button and does not show the update in search result view (no separate view, I display the filteredItems using data source in the same view). Even if I hit it multiple times, it always updates once.

Once I go back to the regular view by canceling search bar, I see 1 up or down depends on which button I tapped. Does anyone know what might be causing the problem here?

class ItemsViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, SortTypeTableViewControllerDelegate, ItemListCellDelegate {
private var items = [Item]()
private var filteredItems = [Item]()

private func retrieveFirebaseData(sortType: ItemSort.SortType, sortOrder: ItemSort.SortOrder) {
guard let currentUser = Auth.auth().currentUser else {
  return print("user not logged in")
}

let itemsRef = DatabaseReferenceHelper.usersRef.child(currentUser.uid).child("items")

itemsRef.queryOrdered(byChild: sortType.rawValue).observe(.value) { (snapshot) in
  var newItems: [Item] = []

  for item in snapshot.children {
    let item = Item(snapshot: item as! DataSnapshot)
    if self.displayFavoritesOnly == true {
      if item.favorite == true {
        newItems.append(item)
      }
    } else {
      newItems.append(item)
    }
  }

  self.items = sortOrder == .ascending ? newItems : newItems.reversed()
  self.collectionView.reloadData()
}
}

// this is from item cell delegate
func increaseDecreaseQuantity(_ sender: ItemListCell, increment: Bool) {
  guard let tappedIndexPath = collectionView.indexPath(for: sender) else {
    return
  }

  let item: Item
  item = isFiltering() ? filteredItems[tappedIndexPath.item] : items[tappedIndexPath.item]

  let updatedQuantity = increment == true ? item.quantity + 1 : item.quantity - 1

  guard let currentUser = Auth.auth().currentUser else {
    return print("user not logged in")
  }

  let itemsRef = DatabaseReferenceHelper.usersRef.child(currentUser.uid).child("items")

  itemsRef.child(item.key).updateChildValues(["quantity": updatedQuantity])

}


// Here's the search logic I learned from Ray Wenderlich
private func searchBarIsEmpty() -> Bool {
  return searchController.searchBar.text?.isEmpty ?? true
}

private func filterContentForSearchText(_ searchText: String, scope: String = "All") {
  filteredItems = items.filter({$0.title.lowercased().contains(searchText.lowercased())})
  collectionView.reloadData()
}

private func isFiltering() -> Bool {
  return searchController.isActive && !searchBarIsEmpty()
}


extension ItemsViewController: UISearchResultsUpdating {

  func updateSearchResults(for searchController: UISearchController) {
    filterContentForSearchText(searchController.searchBar.text!)
  }
}

Solution

  • I fixed the issue. All I did was adding another parameter called "searchText" in retrieveFirebaseData function and use the function in filterContentForSearchText.

    Basically, I needed to filter items UNDER observe method like in the code below.

    private func retrieveFirebaseData(sortType: ItemSort.SortType, sortOrder: ItemSort.SortOrder, searchText: String?) {
    
      guard let currentUser = Auth.auth().currentUser else {
        return print("user not logged in")
      }
    
    
      let itemsRef = DatabaseReferenceHelper.usersRef.child(currentUser.uid).child("items")
    
      itemsRef.queryOrdered(byChild: sortType.rawValue).observe(.value) { (snapshot) in
        if let searchText = searchText {
          // FILTER HERE
          self.filteredItems = self.items.filter({$0.title.lowercased().contains(searchText.lowercased())})
        } else {
          var newItems: [Item] = []
          for item in snapshot.children {
            let item = Item(snapshot: item as! DataSnapshot)
            if self.displayFavoritesOnly == true {
              if item.favorite == true {
                newItems.append(item)
              }
            } else {
              newItems.append(item)
            }
          }        
          self.items = sortOrder == .ascending ? newItems : newItems.reversed()
        }
    
        self.collectionView.reloadData()
      }
    }
    
    private func filterContentForSearchText(_ searchText: String, scope: String = "All") {
      retrieveFirebaseData(sortType: itemSortType, sortOrder: itemSortOrder, searchText: searchText)
    }
    

    "filterContentForSearchText" function used to filter items and reload table like this:

    private func filterContentForSearchText(_ searchText: String, scope: String = "All") {
      filteredItems = items.filter({$0.title.lowercased().contains(searchText.lowercased())})
      collectionView.reloadData()
    }