Issue with Duplicate Messages When navigating back to ViewController - ios

I have a ViewController that shows the user more information, then they click on a button and it sends them to my messageView which is a JSQ ViewController. The JSQViewController uses the collection View classes. When the user navigates back to the more information view and then hits the button to navigate to the message view again, the collection view duplicates my chat bubbles. I think I need to reset something to nil, but I am not sure what to set and where to set it. Here is my code:
import UIKit
import Firebase
import JSQMessagesViewController
// MARK: Properties
var messages = [JSQMessage]()
var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!
let rootReference = FIRDatabase.database().referenceFromURL("myHTTPSLINK")
var messageReference: FIRDatabaseReference!
var dataBaseMessageRef: FIRDatabaseReference!
class ChatViewController: JSQMessagesViewController{
var userContacedID: String = ""
var userToBeChatted : String = ""
var userIsTypingRef: FIRDatabaseReference!
var usersTypingQuery: FIRDatabaseQuery!
private var localTyping = false
var isTyping: Bool {
get {
return localTyping
}
set {
localTyping = newValue
userIsTypingRef.setValue(newValue)
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Now Chatting With: " + userToBeChatted
setupBubbles()
// No avatars
collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero
messageReference = rootReference.child("messages")
dataBaseMessageRef = messageReference.child("\(userToBeChatted)" + "\(FIRAuth.auth()?.currentUser?.displayName)")
print ("This users being messaged is: ", userToBeChatted)
}
override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!,
senderDisplayName: String!, date: NSDate!) {
let itemRef = dataBaseMessageRef.childByAutoId() // 1
let messageItem = [ // 2
"text": text,
"senderId": senderId,
"receiver" : userToBeChatted,
"senderName": FIRAuth.auth()?.currentUser?.displayName,
"receiverID": userContacedID
]
itemRef.setValue(messageItem) // 3
// 4
JSQSystemSoundPlayer.jsq_playMessageSentSound()
// 5
finishSendingMessage()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
observeMessages()
observeTyping()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
}
override func collectionView(collectionView: JSQMessagesCollectionView!,
messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(collectionView: JSQMessagesCollectionView!,
messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item] // 1
if message.senderId == senderId { // 2
return outgoingBubbleImageView
} else { // 3
return incomingBubbleImageView
}
}
override func collectionView(collectionView: JSQMessagesCollectionView!,
avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
private func setupBubbles() {
let factory = JSQMessagesBubbleImageFactory()
outgoingBubbleImageView = factory.outgoingMessagesBubbleImageWithColor(
UIColor.jsq_messageBubbleBlueColor())
incomingBubbleImageView = factory.incomingMessagesBubbleImageWithColor(
UIColor.jsq_messageBubbleRedColor())
}
func addMessage(id: String, text: String) {
let message = JSQMessage(senderId: id, displayName: FIRAuth.auth()?.currentUser?.displayName , text: text)
messages.append(message)
}
override func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath)
as! JSQMessagesCollectionViewCell
let message = messages[indexPath.item]
if message.senderId == senderId {
cell.textView!.textColor = UIColor.whiteColor()
} else {
cell.textView!.textColor = UIColor.whiteColor()
}
return cell
}
private func observeMessages() {
dataBaseMessageRef = messageReference.child("\(userToBeChatted)\(FIRAuth.auth()?.currentUser?.displayName as String!)")
let messagesQuery = dataBaseMessageRef.queryLimitedToLast(25)
messagesQuery.observeEventType(.ChildAdded, withBlock: { snapshot in
let id = snapshot.value!["senderId"] as! String
let text = snapshot.value!["text"] as! String
self.addMessage(id, text: text)
self.finishReceivingMessage()
})
}
override func textViewDidChange(textView: UITextView) {
super.textViewDidChange(textView)
isTyping = textView.text != ""
}
private func observeTyping() {
let typingIndicatorRef = rootReference.child("typingIndicator")
userIsTypingRef = typingIndicatorRef.child(senderId)
userIsTypingRef.onDisconnectRemoveValue()
usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqualToValue(true)
usersTypingQuery.observeEventType(.Value, withBlock: { snapshot in
// You're the only one typing, don't show the indicator
if snapshot.childrenCount == 1 && self.isTyping { return }
// Are there others typing?
self.showTypingIndicator = snapshot.childrenCount > 0
self.scrollToBottomAnimated(true)
})
}
}

in viewDidAppear before observeMessages() insert messages.removeAll() or messages = []

Move observeMessages() from viewDidAppear() and place it in viewDidLoad().
Had the same problem and managed to fix it with the above.

Related

Indexpath row is zero after searching

I have been facing issue after searching of tableview. PDF files stored in Firebase are listed in alphabetic order in tableview and can be opened invidually in detailed view as well as after search an item , filtered item can be also viewed without any problem. However, after back to list and click same filtered pdf without refreshing the list, it gives me the first pdf of the list which is that the indexpath.row is zero.
For example, when I search and click on the item with indexpath.row number 3, I reach the relevant pdf file. But when I come back and click on the same filtered item, it brings up the first item of the whole list. Cant figure out how to handle it. Thank you.
import UIKit
import Firebase
import PDFKit
class IcsViewcontroller: UIViewController , UISearchBarDelegate {
var preImage : UIImage?
let cellSpacingHeight: CGFloat = 20
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var pdfListView: UITableView!
#IBOutlet weak var spinner: UIActivityIndicatorView!
var pdfList = [pdfClass]()
var searchall = [pdfClass]()
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
pdfListView.delegate = self
pdfListView.dataSource = self
searchBar.delegate = self
self.pdfListView.isHidden = true
getPdf()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if searching {
let destination = segue.destination as! PdfKitViewController
let selectedIndexPath = pdfListView.indexPathForSelectedRow
destination.pdf = searchall[selectedIndexPath!.row]
} else {
let destination = segue.destination as! PdfKitViewController
let selectedIndexPath = pdfListView.indexPathForSelectedRow
destination.pdf = pdfList [selectedIndexPath!.row]
}
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
if searchBar.text == nil || searchBar.text == "" {
searching = false
} else {
searching = true
}
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searching = false
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
self.pdfListView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searching = false
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
searchall = pdfList
searching = false
pdfListView.reloadData()
} else {
searching = true
searchall = pdfList.filter({($0.pdf_name?.lowercased().prefix(searchText.count))! == searchText.lowercased() })
pdfListView.reloadData()
}
}
func getPdf () {
spinner.startAnimating()
let docRef = Storage.storage().reference().child("ICS_Documents")
docRef.listAll{ (result , error ) in
if let error = error {
print (error )
}
for item in result.items {
let storeageLocation = String( describing : item)
let gsReference = Storage.storage().reference(forURL: storeageLocation)
gsReference.downloadURL{ url , error in
//self.pdfList.removeAll()
if let error = error{
print(error)
} else {
let pdf_name = String( item.name)
let pdf_url = url?.absoluteString
let thumbnailSize = CGSize(width: 100, height: 100)
let thmbnail = self.generatePdfThumbnail(of: thumbnailSize, for: url!, atPage: 0)
let pdfall = pdfClass(pdf_name: pdf_name, pdf_url: pdf_url!, pdf_preview: thmbnail!)
self.pdfList.append(pdfall)
}
DispatchQueue.main.async {
self.pdfList = self.pdfList.sorted(by: { $0.pdf_name ?? "" < $1.pdf_name ?? ""})
self.pdfListView.reloadData()
self.spinner.stopAnimating()
self.pdfListView.isHidden = false
}
}
}
}
}
func generatePdfThumbnail(of thumbnailSize: CGSize , for documentUrl: URL, atPage pageIndex: Int) -> UIImage? {
let pdfDocument = PDFDocument(url: documentUrl)
let pdfDocumentPage = pdfDocument?.page(at: pageIndex)
return pdfDocumentPage?.thumbnail(of: thumbnailSize, for: PDFDisplayBox.trimBox)
}
}
extension IcsViewcontroller : UITableViewDelegate,UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching{
return searchall.count
}else {
return pdfList.count
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return cellSpacingHeight
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "pdfCell", for: indexPath) as! pdfCellTableViewCell
let varcell : pdfClass
if searching {
varcell = searchall [indexPath.row]
} else {
varcell = pdfList [indexPath.row]
}
cell.configure(name: varcell.pdf_name! , pdfthumbnail: varcell.pdf_preview!)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var indx : pdfClass
if searching{
indx = searchall[indexPath.row ]
print(indexPath.row )
}else {
indx = pdfList[indexPath.row]
}
performSegue(withIdentifier: "toPdfKit", sender: indx)
print(indexPath.row)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action:UITableViewRowAction,indexPath:IndexPath)-> Void in
let storage = Storage.storage()
let childsRURL = self.pdfList[indexPath.row].pdf_url
let storageref = storage.reference(forURL: childsRURL!)
storageref.delete{ error in
if let error = error {
print(error.localizedDescription)
} else{
print("File deleted")
}
}
self.pdfListView.reloadData()
})
return [deleteAction]
}
}
This is the pdfClass
import Foundation
import UIKit
class pdfClass : NSObject {
var pdf_name : String?
var pdf_url : String?
var pdf_preview : UIImage?
override init(){
}
init (pdf_name :String , pdf_url : String, pdf_preview : UIImage ) {
self.pdf_name = pdf_name
self.pdf_url = pdf_url
self.pdf_preview = pdf_preview
}
}
I believe your problem is here, when you click on the cell, your searchBar editing is finished and you make the variable false, changing the list you are working on in the delegate.
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searching = false
}
For simplicity, I suggest keeping the original list just to load the results into a helper list that is used throughout the class, rather than working with two lists in each delegate.
Like this way:
import UIKit
class Shops {
private var _familiy_id: String?
private var _logo : String?
private var _shopname : String?
var familiy_id : String{
return _familiy_id!
}
var shopname : String{
return _shopname!
}
var Logo : String{
return _logo!
}
init(shopname : String , Logo : String , family_id : String) {
self._shopname = shopname
self._logo = Logo
self._familiy_id = family_id
}
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var shops : [Shops]! = []
var auxiliar : [Shops]!
override func viewDidLoad() {
super.viewDidLoad()
// 1 - load data to shops array
shops.append(Shops(shopname: "Brasil", Logo: "BR", family_id: "1"))
shops.append(Shops(shopname: "Brasolia", Logo: "BA", family_id: "2"))
shops.append(Shops(shopname: "Colombia", Logo: "CO", family_id: "3"))
shops.append(Shops(shopname: "Argentina", Logo: "AR", family_id: "4"))
// 2 - auxiliar receive the complete original array
auxiliar = shops
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return auxiliar.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = auxiliar[indexPath.row].shopname
return cell
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
auxiliar = shops.filter { $0.shopname.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil }
if searchText == "" {
// 3 if there is nothing to search, auxiliar receive the complete orihinal array
auxiliar = shops
}
tableView.reloadData()
}
}

How can I take the sum of table view cells' data in Swift?

I'm trying to add up all of the amount data and send to a different table view cell. I think I need to convert String to Double before I can do this, but I'm not sure how to do this either. Does anyone know how you can take the sum of data and present it somewhere else? I'm very new to swift and am having trouble figuring out where I need to write this code.
import Foundation
struct Income: Codable {
var name: String
var amount: String
init(name: String, amount: String) {
self.name = name
self.amount = amount
}
static let DocumentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("incomes").appendingPathExtension("plist")
static func loadSampleIncomes() -> [Income] {
return [
Income(name: "Main Income", amount: "0"),
Income(name: "Secondary Income", amount: "0"),
Income(name: "Interest Income", amount: "0")]
}
static func saveToFile(incomes: [Income]) {
let propertyListEncoder = PropertyListEncoder()
let codedIncomes = try? propertyListEncoder.encode(incomes)
try? codedIncomes?.write(to: ArchiveURL, options: .noFileProtection)
}
static func loadFromFile() -> [Income]? {
guard let codedIncomes = try? Data(contentsOf: ArchiveURL) else { return nil }
let propertyListDecoder = PropertyListDecoder()
return try? propertyListDecoder.decode(Array<Income>.self, from: codedIncomes)
}
}
import UIKit
class IncomeTableViewController: UITableViewController {
var incomes: [Income] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: CGRect.zero)
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
// table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return incomes.count
} else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "incomeCell", for: indexPath) as! IncomeTableViewCell
let income = incomes[indexPath.row]
cell.update(with: income)
cell.showsReorderControl = true
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
override func tableView(_ tableView: UITableView, commit
editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath:
IndexPath) {
if editingStyle == .delete {
incomes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: . automatic)
Income.saveToFile(incomes: incomes)
}
}
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
let movedIncome = incomes.remove(at: fromIndexPath.row)
incomes.insert(movedIncome, at: to.row)
tableView.reloadData()
}
#IBAction func editButtonTapped(_ sender: UIBarButtonItem) {
let tableViewEditingMode = tableView.isEditing
tableView.setEditing(!tableViewEditingMode, animated: true)
}
// Navigation
#IBAction func unwindToIncomeTableView(segue:UIStoryboardSegue) {
guard segue.identifier == "saveIncomeUnwind",
let sourceViewController = segue.source as? AddEditIncomeTableViewController,
let income = sourceViewController.income else { return }
if let selectedIndexPath = tableView.indexPathForSelectedRow {
incomes[selectedIndexPath.row] = income
tableView.reloadRows(at: [selectedIndexPath],
with: .none)
} else {
let newIndexPath = IndexPath(row: incomes.count, section: 0)
incomes.append(income)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
Income.saveToFile(incomes: incomes)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditIncome" {
let indexPath = tableView.indexPathForSelectedRow!
let income = incomes[indexPath.row]
let navController = segue.destination as! UINavigationController
let addEditIncomeTableViewController = navController.topViewController as! AddEditIncomeTableViewController
addEditIncomeTableViewController.income = income
}
}
}
import UIKit
class AddEditIncomeTableViewController: UITableViewController {
#IBOutlet weak var saveButton: UIBarButtonItem!
#IBOutlet weak var incomeNameTextField: UITextField!
#IBOutlet weak var incomeAmountTextField: UITextField!
var income: Income?
override func viewDidLoad() {
super.viewDidLoad()
if let income = income {
incomeNameTextField.text = income.name
incomeAmountTextField.text = income.amount
}
updateSaveButtonState()
}
override func prepare(for segue: UIStoryboardSegue, sender:
Any?) {
super.prepare(for: segue, sender: sender)
guard segue.identifier == "saveIncomeUnwind" else { return }
let name = incomeNameTextField.text ?? ""
let amount = incomeAmountTextField.text ?? ""
income = Income(name: name, amount: amount)
}
func updateSaveButtonState() {
let nameText = incomeNameTextField.text ?? ""
let amountText = incomeAmountTextField.text ?? ""
saveButton.isEnabled = !nameText.isEmpty && !amountText.isEmpty
}
#IBAction func textEditingChanged(_ sender: UITextField) {
updateSaveButtonState()
}
}
this is the table view controller in which I want the new data to be presented.
import UIKit
class BudgetHomeTableViewController: UITableViewController {
var incomes: [Income] = []
var expenses: [Expense] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: CGRect.zero)
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
if let savedExpenses = Expense.loadFromFile() {
expenses = savedExpenses
} else {
expenses = Expense.loadSampleExpenses()
}
}
}
This is the cell where the data will be presented specifically
import UIKit
class BugetBalanceCell: UITableViewCell {
#IBOutlet weak var budgetBalanceText: UILabel!
var incomes: [Income] = []
var expenses: [Expense] = []
override func awakeFromNib() {
super.awakeFromNib()
let incomeTotal = incomes.map({Double($0.amount) ?? 0}).reduce(0, +)
let expenseTotal = expenses.map({Double($0.amount) ?? 0}).reduce(0, +)
let balance = incomeTotal - expenseTotal
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
First define your income,expenses arrays and load data
var incomes: [Income] = []
var expenses: [Income] = []
if let savedIncomes = Income.loadFromFile() {
incomes = savedIncomes
} else {
incomes = Income.loadSampleIncomes()
}
//do the same thing for load data to expenses array
you can get your balance = incomes - expenses using
let incomeTotal = incomes.map({Double($0.amount) ?? 0}).reduce(0, +)
let expenseTotal = expenses.map({Double($0.amount) ?? 0}).reduce(0, +)
let balance = incomeTotal - expenseTotal
use this value where you want to show in next Table View conntroller
(income as NSArray).valueForKeyPath("#sum.self")

Duplicated messages Using JSQMessagerViewController (Swift) (iOS)

I have a JSQMessagersViewController which is connected to a NavigationController. Moreover, I have a ViewController with a tableView with the contacts. Thus, when you click on a cell from the tableView, it opens the JSQMessagerViewController with that conversation. Inside the conversation, when the user sends a message the first time everything works fine. However, once the user goes out the conversation to the tableViewController, if he gets back in a conversation and sends a message, the message gets duplicated by the number of time the user when out and back in. In addition, once the message is duplicated and the user goes out of the conversation, once he click on the same conversation, the message is no longer duplicated. Indeed, when I verify on the database (Firebase), there is no duplicated messages. I can't figure out what is creating this loop.
MessageReceivedDelegate.swift
import Foundation
import Firebase
protocol MessageReceivedDelegate: class {
func message_received(senderID: String, senderName: String, text: String, target: String)
}
class messages_help {
private static let _instance = messages_help()
weak var delegate: MessageReceivedDelegate?
private var currentTarget = String()
static var Instance: messages_help {
return _instance
}
func sendMessage(senderID: String, senderName: String, text: String, target: String) {
let ref = FIRDatabase.database().reference(fromURL: )
let data: Dictionary <String, Any> = [Constants.sender_id: senderID, Constants.sender_name: senderName, Constants.text: text, Constants.target: target]
ref.child("messages").childByAutoId().setValue(data)
}
func getData() {
let ref = FIRDatabase.database().reference(fromURL: )
let user = FIRAuth.auth()?.currentUser
let curr = ref.child("messages")
curr.observe(.childAdded, with: {(snapshot) in
//print(snapshot)
//Get Value from DataBase
print()
if let data = snapshot.value as? NSDictionary {
if let senderID = data[Constants.sender_id] as? String{
if let senderName = data[Constants.sender_name] as? String {
if let target = data[Constants.target] as? String{
if user?.uid == senderID || user?.email == target{
if let text = data[Constants.text] as? String {
self.delegate?.message_received(senderID: senderID, senderName: senderName, text: text, target: target)
}
}
}
}
}
}
},withCancel: nil)
}
}
}
ChatViewController.swift
import UIKit
import JSQMessagesViewController
import MobileCoreServices
import AVKit
import FirebaseAuth
class ChatViewController: JSQMessagesViewController,
MessageReceivedDelegate {
#IBOutlet var targetLabel: UINavigationItem!
var messages = [JSQMessage]()
#IBOutlet var chatLabel: UINavigationItem!
var i = 0
var targetEmail = String()
override func viewDidLoad() {
super.viewDidLoad()
let user = FIRAuth.auth()?.currentUser
messages_help.Instance.delegate = self
self.senderId = user?.uid
self.senderDisplayName = user?.email
self.targetLabel.title = targetEmail
setupBackButton()
// Show Button to simulate incoming messages
self.inputToolbar.contentView.leftBarButtonItem = nil
if Language().check_language() == "Fr"{
self.inputToolbar.contentView.textView.placeHolder = "Nouveau message";
}
else if Language().check_language() == "Es"{
self.inputToolbar.contentView.textView.placeHolder = "Nuevo mensaje";
}
self.inputToolbar.contentView.rightBarButtonItem.setImage(UIImage(named: "paper_plane"), for: .normal)
self.inputToolbar.contentView.rightBarButtonItem.setTitleColor(UIColor.brown, for: .normal)
automaticallyScrollsToMostRecentMessage = true
//self.collectionView?.reloadData()
//messages_help.Instance.getData()
// Do any additional setup after loading the view.
messages.removeAll()
messages_help.Instance.getData()
}
func go() {
let toViewController = storyboard?.instantiateViewController(withIdentifier: "previous") as! PreviousRequestsViewController
//Go to the page
self.present(toViewController, animated:true, completion: nil)
}
//message received
func message_received(senderID: String, senderName: String, text: String, target: String) {
if target == targetEmail || senderName == targetEmail {
//print("Number of loop\n")
//i = i+1
// print(i)
/**
* Scroll to actually view the indicator
*/
self.scrollToBottom(animated: true)
messages.append(JSQMessage(senderId: senderID, displayName: senderName, text: text))
}
collectionView.reloadData()
}
// Number of rows
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
// Cell
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
return cell
}
// Display messages
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
// Avatar
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
return JSQMessagesAvatarImageFactory.avatarImage(with: UIImage(named: "iTunesArtwork"), diameter: 30)
}
//the bubble
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let bubble_fact = JSQMessagesBubbleImageFactory()
let message = messages[indexPath.item]
if message.senderId == self.senderId {
return bubble_fact?.outgoingMessagesBubbleImage(with: UIColor.brown)
}
else {
return bubble_fact?.incomingMessagesBubbleImage(with: UIColor.darkGray)
}
}
// When pressed sent
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
messages_help.Instance.sendMessage(senderID: senderId, senderName: senderDisplayName, text: text, target: self.targetEmail )
//dismiss text from text fild
finishSendingMessage()
}
func setupBackButton() {
if Language().check_language() == "Fr"{
let backButton = UIBarButtonItem(title: "Retour", style: UIBarButtonItemStyle.plain, target: self, action: #selector(backButtonTapped))
backButton.tintColor = UIColor.brown
navigationItem.leftBarButtonItem = backButton
}
else if Language().check_language() == "Es"{
let backButton = UIBarButtonItem(title: "Regresa", style: UIBarButtonItemStyle.plain, target: self, action: #selector(backButtonTapped))
backButton.tintColor = UIColor.brown
navigationItem.leftBarButtonItem = backButton
} else {
let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(backButtonTapped))
backButton.tintColor = UIColor.brown
navigationItem.leftBarButtonItem = backButton
}
}
func backButtonTapped() {
self.dismiss(animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
}
ContactChatViewController.swift
import UIKit
import Firebase
class ContactChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet var chat_TableView: UITableView!
var ref: FIRDatabaseReference!
var publish = [String?]()
var data_to_send = String()
var encountered = Set<String>()
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference(fromURL: )
getDataa()
}
// get data from trips
func getDataa() {
// Reference to current user
let user = FIRAuth.auth()?.currentUser
let curr = ref.child("messages")
curr.observe(.childAdded, with: {(snapshot) in
if let data = snapshot.value as? [String: AnyObject] {
let use = messages()
use.setValuesForKeys(data)
//Append new data if same user
if user?.email == use.target || user?.email == use.sender_name {
if self.encountered.contains(use.sender_name!) || (user?.email)! == use.sender_name! {
//print("Already inside")
} else {
self.encountered.insert(use.sender_name!)
self.publish.append(use.sender_name)
}
//Appen to to array even if current user sent a message but is not the target
if user?.email == use.sender_name{
if self.encountered.contains(use.target!) {
print("Already inside")
} else {
self.encountered.insert(use.target!)
self.publish.append(use.target)
}
}
}
//Load data in U Thread
DispatchQueue.main.async {
self.chat_TableView.reloadData()
}
}
}
,withCancel: nil)
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
// hide separators
tableView.separatorStyle = .none
//return count with database
return publish.count
}
// Assign rows a value
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell_user = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "previous_cell")
cell_user.selectionStyle = .none
cell_user.backgroundColor = UIColor.clear
cell_user.textLabel?.textColor = UIColor.brown
cell_user.textLabel?.font = UIFont.systemFont(ofSize: 25)
// Check for duplicates
cell_user.textLabel?.text = self.publish[indexPath.row]
return cell_user
}
//Clicked on a cell
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
data_to_send = self.publish[indexPath.row]!
self.performSegue(withIdentifier: "chatSegue", sender: self)
//let chatView = ChatViewController()
//chatView.targetEmail = self.publish[indexPath.row]!
//let chatNavigationController = UINavigationController(rootViewController: chatView)
//present(chatNavigationController, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//let toViewController = storyboard?.instantiateViewController(withIdentifier: "chatPage") as! ChatViewController
//toViewController.targetEmail = data_to_send
//Go to the page
//self.present(toViewController, animated:true, completion: nil)
let navVC = segue.destination as? UINavigationController
let chatVC = navVC?.viewControllers.first as! ChatViewController
chatVC.targetEmail = data_to_send
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

Problems while trying to filter dictionary - index out of range error

I have a journal application that has an object called Entry. It has its own Swift file called Entry.swift and these journal entries are saved using arrays of dictionaries.
I added a search bar to the UITableViewController and whenever I input a letter the app crashes after tableView.reloadData() is called. I think this has something to do with the filter returning my array of dictionaries named entries incorrectly and when tableView.reloadData() is called, both of the labels on the dequeueReusableCell can't be populated because the information is in the wrong format in the array of dictionaries.
Entry.swift
//
// Entry.swift
// Journal
//
// Created by handje on 6/17/17.
// Copyright © 2017 Rob Hand. All rights reserved.
//
import Foundation
class Entry {
static fileprivate let titleKey = "title"
static fileprivate let bodyTextKey = "bodyText"
static fileprivate let dateKey = "date"
var title: String
var bodyText: String
var date: String
init(title: String, bodyText: String, date: String ) {
self.title = title
self.bodyText = bodyText
self.date = date
}
func dictionaryRepresentation() -> [String: Any] {
return [Entry.titleKey: title, Entry.bodyTextKey: bodyText, Entry.dateKey: date]
}
convenience init?(dictionary: [String: Any]) {
guard let title = dictionary[Entry.titleKey] as? String,
let bodyText = dictionary[Entry.bodyTextKey] as? String, let date = dictionary[Entry.dateKey] as? String else { return nil
}
self.init(title: title, bodyText: bodyText, date: date)
}
}
extension Entry: Equatable {
static func == (lhs:Entry, rhs:Entry) -> Bool {
return
lhs.title == rhs.title &&
lhs.bodyText == rhs.bodyText
}
}
EntryListTableViewController.swift
//
// EntryListTableViewController.swift
// Journal
//
// Created by handje on 6/17/17.
// Copyright © 2017 Rob Hand. All rights reserved.
//
import UIKit
class EntryListTableViewCell: UITableViewCell {
#IBOutlet weak var dreamTitle: UILabel!
#IBOutlet weak var dreamDate: UILabel!
}
extension EntryListTableViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
}
class EntryListTableViewController: UITableViewController {
#IBOutlet weak var searchBar: UISearchBar!
var dreamTitle: UILabel!
let searchController = UISearchController(searchResultsController: nil)
let dreams = EntryController.shared.entries
var filteredDreams = [Entry]()
func filterContentForSearchText(searchText: String, scope: String = "All") {
let filteredDreams = EntryController.shared.entries.filter{ $0.title.contains(searchController.searchBar.text!) }
tableView.reloadData()
print(filteredDreams)
}
override func viewDidLoad() {
//cell setup
super.viewDidLoad()
let backgroundImage = UIImage(named: "DreamPageLucidity.jpg")
let imageView = UIImageView(image: backgroundImage)
imageView.contentMode = .scaleAspectFill
self.tableView.backgroundView = imageView
tableView.separatorInset = .zero
tableView.separatorColor = UIColor.lightGray
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return EntryController.shared.entries.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "entryCell", for: indexPath) as! EntryListTableViewCell
let entry: Entry
if searchController.isActive && searchController.searchBar.text != "" {
entry = filteredDreams[indexPath.row] /////////ERROR HERE///////
} else {
entry = EntryController.shared.entries[indexPath.row]
}
cell.dreamTitle.text = entry.title
cell.dreamDate.text = entry.date
if cell.dreamTitle.text == "" {
cell.dreamTitle.text = "Untitled Dream"
}
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let entry = EntryController.shared.entries[indexPath.row]
EntryController.shared.deleteEntry(entry: entry)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let detailVC = segue.destination as? EntryDetailViewController
guard let indexPath = tableView.indexPathForSelectedRow else { return }
let entry = EntryController.shared.entries[indexPath.row]
detailVC?.entry = entry
}
}
EntryContoller.swift
//
// EntryController.swift
// Journal
//
// Created by handje on 6/17/17.
// Copyright © 2017 Rob Hand. All rights reserved.
//
import Foundation
class EntryController {
var entries = [Entry]()
static fileprivate let entriesKey = "entriesKey"
static let shared = EntryController()
init() {
load()
}
// MARK: - CRUD
func addNewEntryWith(title: String, bodyText: String, date: String) {
let entry = Entry(title: title, bodyText: bodyText, date: date)
entries.append(entry)
save()
}
func updateEntry(entry: Entry, title: String, bodyText: String, date: String) {
entry.title = title
entry.bodyText = bodyText
save()
}
// Set up search bar
func deleteEntry(entry: Entry) {
guard let index = entries.index(of: entry) else { return }
entries.remove(at: index)
save()
}
// MARK: - save/load UserDefaults
private func save() {
let entryDictionaries = entries.map {$0.dictionaryRepresentation()}
UserDefaults.standard.set(entryDictionaries, forKey: EntryController.entriesKey)
}
private func load() {
guard let entryDictionaries = UserDefaults.standard.object(forKey: EntryController.entriesKey) as? [[String: Any]] else { return }
entries = entryDictionaries.flatMap ({ Entry(dictionary: $0) })
}
}
I think there is a problem with
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return EntryController.shared.entries.count
}
You should put a check here that if search controller is active then return count from filteredDreams else return count from EntryController.shared.entries.count, (make code changes as per your exact implementation) something like:
if searchController.isActive && searchController.searchBar.text != "" {
return filterDreams.count
} else {
return EntryController.shared.entries.count
}
You're returning a list that is bigger than the filtered list in the delegate function numberOfRowsInSection
try this, when searching:
func filterContentForSearchText(searchText: String, scope: String = "All") {
// update the list that is a class property, you were creating a new one
if searchText.isEmpty {
filteredDreams = EntryController.shared.entries
} else {
filteredDreams = EntryController.shared.entries.filter{ $0.title.contains(searchController.searchBar.text!) }
}
tableView.reloadData()
}
in numberOfRowsInSection
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// use the filtered list to determine count
return filteredDreams.count
}
for more safety you can return an empty cell instead of crashing:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard indexPath.row < filteredDreams.count else { return UITableViewCell() }
// your code here
}

Adding Searchbar to table view returns unwrapping nil view

Im attempting to add a search bar to a table view that displays names. Im getting a unwrapping nil error when i try to add it as a subheader to my tableview. Im not sure why. The point where the error occurs is marked.
import UIKit
class WebpageController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchResultsUpdating {
var names: [String] = [String]()
var school: String = String()
var index: Int = Int()
var searchResults: [String] = [String]()
var searchController = UISearchController(searchResultsController: nil)
#IBOutlet weak var tables: UITableView!
var refreshControl: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.sizeToFit()
definesPresentationContext = true
tables.tableHeaderView = searchController.searchBar //returns error
names = HtmlController.loadData() as NSArray as! [String]
clean()
refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(WebpageController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
tables.addSubview(refreshControl)
// Do any additional setup after loading the view.
}
func refresh(sender: AnyObject)
{
names = HtmlController.loadData() as NSArray as! [String]
clean()
tables.reloadData()
refreshControl.endRefreshing()
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchText = searchController.searchBar.text
filterContentForSearchText(searchText!)
tables.reloadData()
}
func filterContentForSearchText(searchText: String)
{
if(searchText == "")
{
searchResults = names
}
else{
searchResults = names.filter({ ( a: String) -> Bool in
let nameMatch = a.rangeOfString(searchText, options:
NSStringCompareOptions.CaseInsensitiveSearch)
return nameMatch != nil
})
}
}
#IBOutlet weak var Table: UITableView!
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func clean()
{
var length = names.count
var i = 0;
var bool = false
let defaults = NSUserDefaults.standardUserDefaults()
if defaults.objectForKey("School") == nil
{
school = "11Staff"
defaults.setObject("11", forKey: "School")
}
else{
school = (defaults.objectForKey("School") as! String) + "Staff"
}
var extra: [String] = [String]()
let bundleidentifier = NSBundle.mainBundle().bundleIdentifier
if let aStreamReader = StreamReader(path: NSBundle.mainBundle().pathForResource(school, ofType: "txt")!)
{
defer {
aStreamReader.close()
}
while let line = aStreamReader.nextLine() {
extra.append(line)
}
}
for String in extra
{
while i < length && bool == false
{
if((String.rangeOfString(names[i].uppercaseString)) != nil)
{
names.removeAtIndex(i)
i -= 1
bool = true
length = names.count
}
i+=1;
}
bool = false
i = 0;
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if searchController.active
{
return searchResults.count
}
else{
return names.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell
if let reusedCell = tableView.dequeueReusableCellWithIdentifier("Cell") {
cell = reusedCell
} else {
cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")
}
if let label = cell.textLabel {
label.text = names[indexPath.row + 1].uppercaseString
}
return cell }
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
index = indexPath.row
performSegueWithIdentifier("WebTransfer", sender:self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let Destination: WebsiteController = segue.destinationViewController as! WebsiteController
Destination.index = index
}
}

Resources