Duplicated messages Using JSQMessagerViewController (Swift) (iOS) - 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()
}
}

Related

How to make the deleteTask method work properly?

I am trying to create a ToDoList in swift UI and my deleteTask function is not working properly. I tried a lot of things and none of them worked.
import UIKit
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
var toDoTasks = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "SAVED TASKS"
tableView.delegate = self
tableView.dataSource = self
// setup
if !UserDefaults().bool(forKey: "setup")
{
UserDefaults().set(true, forKey: "setup")
UserDefaults().set(0, forKey: "count")
}
updateTasks()
}
func updateTasks()
{
toDoTasks.removeAll()
guard let count = UserDefaults().value(forKey: "count") as? Int else
{
return
}
for x in 0..<count
{
if let task = UserDefaults().value(forKey: "task_\(x+1)") as? String
{
toDoTasks.append(task)
}
}
tableView.reloadData()
}
#IBAction func didTapAdd()
{
let viewController = storyboard?.instantiateViewController(withIdentifier: "entry") as! EntryViewController
viewController.title = "NEW TASK"
viewController.updated =
{
DispatchQueue.main.async {
self.updateTasks()
}
}
navigationController?.pushViewController(viewController, animated: true)
}
}
extension ViewController: UITableViewDelegate
{
// function to select the rows
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let viewController = storyboard?.instantiateViewController(withIdentifier: "task") as! TasksViewController
viewController.title = "NEW TASK"
viewController.task = toDoTasks[indexPath.row]
navigationController?.pushViewController(viewController, animated: true)
}
}
extension ViewController: UITableViewDataSource
{
// function which returns number of tasks
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toDoTasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = toDoTasks[indexPath.row]
return cell
}
}
import UIKit
class TasksViewController: UIViewController {
#IBOutlet var label : UILabel!
var task : String?
var currentPosition: Int?
override func viewDidLoad() {
super.viewDidLoad()
label.text = task
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Delete", style: .done, target: self, action: #selector(deleteTask))
}
#objc func deleteTask(_ sender: UIBarButtonItem) {
guard let currentPosition = self.currentPosition else {
return
}
let count = UserDefaults.standard.integer(forKey: "count")
UserDefaults.standard.removeObject(forKey: "task_\(currentPosition+1)")
for i in currentPosition+1..<count {
let task = UserDefaults.standard.string(forKey: "task_\(i+1)")
UserDefaults.standard.setValue(task, forKey: "task_\(i)")
}
UserDefaults.standard.setValue(count-1, forKey: "count")
navigationController?.popViewController(animated: true)
}
}
When I press the Delete button, nothing happens. Could you please help me??

Access correct indexPath Row for user

I'm creating a messaging app where users can select who do they want to chat with by a UITableView, the problem is that obviously there needs to be a way to search for an specific user, I had already implemented a UISearchController and I can find the user which I search for. The real problem starts when I select the user with override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) method, because when I select the user, it selects wrong user because of indexPath.row.
Here is some of my code:
NewMessageController:
import UIKit
import Firebase
import FirebaseDatabase
class NewMessageController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
var searchController = UISearchController()
var activityIndicator = UIActivityIndicatorView(style: .large)
var aiView = UIView()
let cellId = "cellId"
var users = [User]()
var filteredUsers = [User]()
override func viewDidLoad() {
super.viewDidLoad()
initSearchController()
setUpActivityIndicator()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(didTapCancelButton))
tableView.register(UserCell.self, forCellReuseIdentifier: cellId)
startAI()
fetchUser()
}
func initSearchController() {
searchController.loadViewIfNeeded()
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.enablesReturnKeyAutomatically = false
searchController.searchBar.returnKeyType = UIReturnKeyType.done
definesPresentationContext = true
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchController.searchBar.scopeButtonTitles = ["All"]
searchController.searchBar.delegate = self
}
func fetchUser() {
Database.database().reference().child("users").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User(dictionary: dictionary)
user.id = snapshot.key
// user.setValuesForKeys(dictionary)
self.users.append(user)
DispatchQueue.main.async {
self.stopAI()
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
func setUpActivityIndicator() {
aiView.layer.zPosition = 0.1
aiView.backgroundColor = UIColor.gray
aiView.alpha = 0
aiView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(aiView)
aiView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true
aiView.centerYAnchor.constraint(equalTo: tableView.centerYAnchor, constant: -60).isActive = true
aiView.heightAnchor.constraint(equalToConstant: 150).isActive = true
aiView.widthAnchor.constraint(equalToConstant: 150).isActive = true
aiView.layer.masksToBounds = true
aiView.layer.cornerRadius = 15
activityIndicator.layer.zPosition = 0.2
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: aiView.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: aiView.centerYAnchor).isActive = true
}
func startAI() {
activityIndicator.startAnimating()
aiView.alpha = 0.80
tableView.isUserInteractionEnabled = false
}
func stopAI() {
self.activityIndicator.stopAnimating()
self.tableView.isUserInteractionEnabled = true
self.aiView.alpha = 0
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
var messagesViewController: MessagesViewController?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss(animated: true) {
let selectedUser: User!
if(self.searchController.isActive)
{
selectedUser = self.filteredUsers[indexPath.row]
}
else
{
selectedUser = self.users[indexPath.row]
}
self.messagesViewController?.showChatControllerForUser(user: selectedUser)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (searchController.isActive) {
return filteredUsers.count
}
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
startAI()
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
let thisUser: User!
if (searchController.isActive) {
thisUser = filteredUsers[indexPath.row]
} else {
thisUser = users[indexPath.row]
}
cell.nameLabel.text = "\(thisUser.firstname!) \(thisUser.surname!)"
cell.usernameLabel.text = thisUser.username
cell.profileImageView.loadImageUsingCacheWithUrlString(urlString: thisUser.userImg!)
cell.timeLabel.text = nil
stopAI()
return cell
}
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let scopeButton = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
let searchText = searchBar.text!
filterForSearchTextAndScopeButton(searchText: searchText, scopeButton: scopeButton)
}
func filterForSearchTextAndScopeButton(searchText: String, scopeButton : String = "All") {
filteredUsers = users.filter {
user in
let scopeMatch = (scopeButton == "All" || user.username!.lowercased().contains(scopeButton.lowercased()))
if(searchController.searchBar.text != "") {
let searchTextMatch = user.username!.lowercased().contains(searchText.lowercased())
return scopeMatch && searchTextMatch
} else {
return scopeMatch
}
}
tableView.reloadData()
}
}
UserModel:
import UIKit
class User: NSObject {
#objc var id: String?
#objc var firstname: String?
#objc var surname: String?
#objc var email: String?
#objc var username: String?
#objc var userImg: String?
init(dictionary: [String: AnyObject]) {
self.id = dictionary["id"] as? String
self.firstname = dictionary["firstname"] as? String
self.surname = dictionary["surname"] as? String
self.username = dictionary["username"] as? String
self.email = dictionary["email"] as? String
self.userImg = dictionary["userImg"] as? String
}
}
The function I use for showing ChatLogController:
#objc func showChatControllerForUser(user: User) {
let chatLogController = ChatLogController(collectionViewLayout: UICollectionViewFlowLayout())
chatLogController.user = user
navigationController?.pushViewController(chatLogController, animated: true)
}
change the way to detect if is search active or not like this.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dismiss(animated: true) {
let selectedUser: User!
if(self.searchController.searchBar.text != "")
{
selectedUser = self.filteredUsers[indexPath.row]
}
else
{
selectedUser = self.users[indexPath.row]
}
self.messagesViewController?.showChatControllerForUser(user: selectedUser)
}
}
some times searchController is active but ther searhbar is "" so is not realizable way to check where is search term or not

Set labels in a custom cell to names from an array using a separate controller

I have a view controller with a table. The view controller has a button that segues to another controller with 4 textfields. There is a name textfield and three others where numbers can be inputed. When a user presses add, an array is appended and the view controller is dismissed while updating the table in the previous view controller. The user is then presented a table with a new cell added. The cell has 4 labels.
I am trying to figure out how to set each label in a cell to the 4 textfields that are in the view controller that created the new cell. Here is a picture of the view controller with a cell created:
Here is the view controller when the plus button is pressed that creates the cell by updating the array:
I would like to be able to set the far left label to represent the name that is added to the array every time. The first time a person clicks the plus button, the textfields view controller will pop up and the the user can put the name in the textfield. When the add button is pressed the label on the left would show the name in the controller that appended the array. The next name that is added through the view controller will be in the cell below the previous name entered and so forth. I need to get the array to show the text properly. I originally wanted the cells to have textfields that the user could put the info in there but the cells data wasn't sending to firebase database properly.
Here is the code for the first view controller:
import UIKit
import Firebase
class ConsiderationsTestViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var tableView: UITableView!
static var numberOfPeople: [String] = []
var AddPersonCell = "AddPersonCell"
#IBOutlet weak var CompanyImage: UIImageView!
#IBOutlet weak var companyNameTextFieldConsiderations: UITextField!
#IBOutlet weak var companyDescriptionTextFieldConsiderations: UITextField!
#IBOutlet weak var startDateTextFieldConsiderations: UITextField!
#IBOutlet weak var endDateTextFieldConsiderations: UITextField!
let datePickerS = UIDatePicker()
let datePickerE = UIDatePicker()
var database: Database!
var storage: Storage!
var selectedImage: UIImage?
var ref:DatabaseReference?
var databaseHandle:DatabaseHandle = 0
let dbref = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.companyNameTextFieldConsiderations.delegate = self
self.companyDescriptionTextFieldConsiderations.delegate = self
// Set the Firebase reference
ref = Database.database().reference()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ConsiderationsTestViewController.handleSelectCompanyImageView))
CompanyImage.addGestureRecognizer(tapGesture)
CompanyImage.isUserInteractionEnabled = true
self.navigationController!.navigationBar.isTranslucent = false
navigationItem.backBarButtonItem = UIBarButtonItem(
title: "", style: .plain, target: nil, action: nil)
createDatePickerForStart()
createDatePickerForEnd()
NotificationCenter.default.addObserver(self, selector: #selector(loadList), name: NSNotification.Name(rawValue: "load"), object: nil)
}
#objc func loadList(){
//load data here
self.tableView.reloadData()
}
#objc func handleSelectCompanyImageView() {
let pickerController = UIImagePickerController()
pickerController.delegate = self
pickerController.allowsEditing = true
present(pickerController, animated: true, completion: nil)
}
#IBAction func AddPersonTapped(_ sender: Any) {
}
#IBAction func sendButtonTapped(_ sender: Any) {
let companyNameC = companyNameTextFieldConsiderations.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let companyDescriptionC = companyDescriptionTextFieldConsiderations.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let today = Date()
let formatter1 = DateFormatter()
formatter1.dateFormat = "MMM d y"
print(formatter1.string(from: today))
let todaysDate = formatter1.string(from: today)
let storageRef = Storage.storage().reference(forURL: "STORAGE URL HERE")
let imageName = companyNameTextFieldConsiderations.text!
let storageCompanyRef = storageRef.child("Company_Image_Considerations").child("\(todaysDate)").child(imageName)
let companyDescriptionTextFieldText = companyDescriptionTextFieldConsiderations.text
let dateToStart = startDateTextFieldConsiderations.text
let dateToDecide = endDateTextFieldConsiderations.text
let companyRef = Database.database().reference().child("Considerations").child("\(todaysDate)").child(imageName)
let considerationInfluencerRef = Database.database().reference().child("Considerations").child("\(todaysDate)").child(imageName).child("Users")
//let values = ["Feed_Quantity": "feedTFC", "Story_Quantity": "storyTFC", "Compensation": "compensationTFC"]
guard let imageSelected = self.CompanyImage.image else {
print ("Avatar is nil")
return
}
var dict: Dictionary<String, Any> = [
"Company Image": "",
"Company Description": companyDescriptionTextFieldText!,
"Start Date": dateToStart!,
"Decision Date": dateToDecide! ]
guard let imageData = imageSelected.jpegData(compressionQuality: 0.5) else {
return
}
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
storageCompanyRef.putData(imageData, metadata: metadata, completion:
{ (StorageMetadata, error) in
if (error != nil) {
return
}
storageCompanyRef.downloadURL { (url, error) in
if let metadateImage = url?.absoluteString {
dict["Company Image"] = metadateImage
companyRef.updateChildValues(dict, withCompletionBlock: {
(error, ref) in
if error == nil {
print("Done")
return
}
}
)
}
}
storageRef.updateMetadata(metadata) { metadata, error in
if error != nil {
//Uh-oh, an error occurred!
} else {
// Updated metadata for 'images/forest.jpg' is returned
}
}
})
// considerationInfluencerRef.updateChildValues(values as [AnyHashable : Any]) { (error, ref) in
// if error != nil {
// print(error ?? "")
// return
// }
self.navigationController?.popViewController(animated: true)
// }
}
func createDatePickerForStart() {
// center text in field
startDateTextFieldConsiderations.textAlignment = .center
// toolbar
let toolbar = UIToolbar()
toolbar.sizeToFit()
// barbutton
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressedStart))
toolbar.setItems([doneButton], animated: true)
// assign toolbar to textfield
startDateTextFieldConsiderations.inputAccessoryView = toolbar
// assign datePicker to text field
startDateTextFieldConsiderations.inputView = datePickerS
// date picker mode
datePickerS.datePickerMode = .date
}
func createDatePickerForEnd() {
// center text in field
endDateTextFieldConsiderations.textAlignment = .center
// toolbar
let toolbar = UIToolbar()
toolbar.sizeToFit()
// barbutton
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressedEnd))
toolbar.setItems([doneButton], animated: true)
// assign toolbar to textfield
endDateTextFieldConsiderations.inputAccessoryView = toolbar
// assign datePicker to text field
endDateTextFieldConsiderations.inputView = datePickerE
// date picker mode
datePickerE.datePickerMode = .dateAndTime
}
#objc func donePressedStart() {
// formatter
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
startDateTextFieldConsiderations.text = formatter.string(from: datePickerS.date)
self.view.endEditing(true)
}
#objc func donePressedEnd() {
// formatter
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .medium
endDateTextFieldConsiderations.text = formatter.string(from: datePickerE.date)
self.view.endEditing(true)
}
#IBAction func testTapped(_ sender: Any) {
print(ConsiderationsTestViewController.numberOfPeople)
}
}
extension ConsiderationsTestViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
//print("did Finish Picking Media")
if let image = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerEditedImage")] as? UIImage{
selectedImage = image
CompanyImage.image = image
}
dismiss(animated: true, completion: nil)
}
}
extension ConsiderationsTestViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ConsiderationsTestViewController.numberOfPeople.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: AddPersonCell, for: indexPath) as! ConsiderationsCell
return cell
}
}
Here is the code for the second view controller:
import UIKit
import Firebase
class DropDownCellViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UITextFieldDelegate {
var numberOfPeople = [String]()
var users = [User]()
var userName = [String]()
var filteredNames: [String]!
let dropDownCell = "dropDownCell"
var emptyField = [String]()
override func viewDidLoad() {
super.viewDidLoad()
updateDataArray()
tableView.register(UserCell.self, forCellReuseIdentifier: dropDownCell)
filteredNames = userName
tableView.delegate = self
tableView.dataSource = self
nameTextField.delegate = self
tableView.isHidden = true
// Manage tableView visibility via TouchDown in textField
nameTextField.addTarget(self, action: #selector(textFieldActive), for: UIControl.Event.touchDown)
}
#IBOutlet weak var nameTextField: NoCopyPasteUITextField!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var gridTextField: UITextField!
#IBOutlet weak var storyTextField: UITextField!
#IBOutlet weak var compensationTextField: UITextField!
#IBAction func textFieldChanged(_ sender: AnyObject) {
tableView.isHidden = true
}
#IBAction func cancelButtonTapped(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
#IBAction func addButtonTapped(_ sender: Any) {
ConsiderationsTestViewController.numberOfPeople.append("\(nameTextField.text!)")
self.navigationController?.popViewController(animated: true)
print(ConsiderationsTestViewController.numberOfPeople)
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
}
}
let searchController = UISearchController(searchResultsController: nil)
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
guard let touch:UITouch = touches.first else
{
return;
}
if touch.view != tableView
{
nameTextField.endEditing(true)
tableView.isHidden = true
}
}
#objc func textFieldActive() {
tableView.isHidden = !tableView.isHidden
}
// MARK: UITextFieldDelegate
func textFieldDidEndEditing(_ textField: UITextField) {
// TODO: Your app can do something when textField finishes editing
print("The textField ended editing. Do something based on app requirements.")
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func numberOfSections(in 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
return filteredNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: dropDownCell, for: indexPath) as!
UserCell
let user = users[indexPath.row]
cell.textLabel?.text = user.name
if let profileImageUrl = user.profileImageUrl {
cell.profileImageView.loadImageUsingCacheWithUrlString(urlString: profileImageUrl)
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 72
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Row selected, so set textField to relevant value, hide tableView
// endEditing can trigger some other action according to requirements
nameTextField.text = userName[indexPath.row]
tableView.isHidden = true
nameTextField.endEditing(true)
}
//Mark: Search Bar Config
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredNames = []
if searchText == "" {
filteredNames = userName
}
else {
for names in userName {
if names.lowercased().contains(searchText.lowercased()) {
filteredNames.append(names)
}
}
}
self.tableView.reloadData()
}
func updateDataArray() {
Database.database().reference().child("users").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
//self.setValuesForKeys(dictionary)
user.name = dictionary["name"] as? String
user.email = dictionary["email"] as? String
user.facebookUrl = dictionary["facebookUrl"] as? String
user.instagramUrl = dictionary["instagramUrl"] as? String
user.linkedInUrl = dictionary["linkedInUrl"] as? String
user.profileImageUrl = dictionary["profileImageUrl"] as? String
user.twitterUrl = dictionary["twitterUrl"] as? String
user.id = dictionary["id"] as? String
user.userType = dictionary["userType"] as? String
self.users.append(user)
self.userName.append(user.name!)
self.filteredNames.append(user.name!)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
}
I am trying to append the label with a function that is called in the addButton function called renameLabel. Here is the func rename label code:
func renameLabel() {
let cell = ConsiderationsTestViewController.init().tableView.dequeueReusableCell(withIdentifier: AddPersonCell) as! ConsiderationsCell
cell.nameLabelC.text = ("\(ConsiderationsTestViewController.numberOfPeople)")
}
I am now getting the error:
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Any help would be awesome!
First of all create a protocol above second VC.
protocol UpdatingArrayDelegate: class {
func updateArray(newArray: [String])
}
And inside the second VC create:
var updatingArrayDelegate: UpdatingArrayDelegate!
Then before pushing to the second VC, set the delegate to self:
#objc func pushToSecondVCButtonTapped() {
let destVC = secondVC()
updatingArrayDelegate = self
self.navigationController?.pushViewController(destVC, animated: true)
// if you don't have self.navigationController use:
// present(destVC(), animated: true, completion: nil)
}
Now when finishing the edit on secondVC when pressing your addButton do the following:
#objc func doneButtonTapped() {
updatingArrayDelegate?.updateArray(newArray: myNewArrayCreatedOnThisSecondVC)
self.navigationController?.popViewController(animated: true)
}
Then add the delegate function on first
extension FirstVC: UpdatingArrayDelegate {
func updateArray(newArray: [String]) {
print("updateArray")
let myCell = myTableView.cellForRow(at: IndexPath(row: x, section: y)) as! MyCell
myCell.textLabel1.text = newArray[0]
myCell.textLabel2.text = newArray[1]
myCell.textLabel3.text = newArray[2]
myCell.textLabel4.text = newArray[3]
}
}
You can think about delegate like this:
The boss (secondVC) gives a the worker (firstVC) a protocol of what to do. The Worker reads the protocol and is doing the work with his function given in the protocol. Before the worker runs to the boss, he must be sure that he can do the next task (written on the protocol he is going to have.)

Swift Xcode 13 Programmatic UITableViewController nil delegate

The Delegated function fires but crashes as its nil, the objects in the items array is populated by CoreData, this var model: CoreDataModel = CoreDataModel(CoreDataController.shared) has to be instantiated rather than as expected in the viewDidLoad to prevent a nil error for the table view row count (model.items.count)
On startup the items array is the complete Sqlite DB, on search its the subset of the table and printing to the console proves the array is changed and only has the subset of Albums.
BaseViewController
import UIKit
import CoreData
protocol UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
}
protocol MasterModel {
var client: LastFMClient { get }
func searchFeed(with userSearchTerm: String?, completion: #escaping (Bool) -> Void)
}
protocol DataReloadTableViewDelegate: class {
func reloadAlbumsTable()
}
class BaseViewController: UITableViewController, MasterModel {
let cellId = "sdlfjowieurewfn3489844224947824dslaksjfs;ad"
let logoContainer = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
let image = UIImage(named: "lastFMRedBlack")
let searchBar = UISearchBar()
let client = LastFMClient()
var model: CoreDataModel = CoreDataModel(CoreDataController.shared)
private var searchResults: Root?
override func viewDidLoad() {
super.viewDidLoad()
setupSearchController()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
tableView.register(SubtitleTableViewCell.self, forCellReuseIdentifier: cellId)
tableView.tableFooterView = UIView(frame: CGRect.zero)
tableView.separatorColor = UIColor(red: 72.5/255, green: 0/255, blue: 0/255, alpha: 1)
imageView.contentMode = .scaleAspectFit
imageView.image = image
logoContainer.addSubview(imageView)
navigationItem.titleView = logoContainer
print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))
model.delegate = self
model.fetchAllAlbums()
}
// MARK - SearchBar
private func setupSearchController() {
searchBar.sizeToFit()
searchBar.placeholder = "Search for Album"
searchBar.delegate = self
showSearchBarButton(shouldShow: true)
}
func showSearchBarButton (shouldShow: Bool) {
if shouldShow {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(handleShowSearchBar))
} else {
searchBar.showsCancelButton = true
navigationItem.rightBarButtonItem = nil
}
}
func search(shouldShow: Bool) {
showSearchBarButton(shouldShow: !shouldShow)
navigationItem.titleView = shouldShow ? searchBar : logoContainer
}
#objc func handleShowSearchBar(){
search(shouldShow: true)
searchBar.becomeFirstResponder()
}
// MARK - API Request
func searchFeed(with userSearchTerm: String?, completion: #escaping (Bool) -> Void) {
// Use the API to get data
client.getFeed(from: LastFMRequest.albumSearch(userSearchTerm: userSearchTerm) ) { result in
switch result {
case .success(let data):
do {
let data = try DataParser.parse(data, type: RootNode.self)
self.searchResults = data.results
completion(true)
} catch {
print(error.localizedDescription)
completion(false)
}
case .failure(let error):
print(error.localizedDescription)
completion(false)
}
}
}
}
extension BaseViewController: UISearchBarDelegate {
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBar.text = nil
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
search(shouldShow: false)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let searchTextString = searchBar.text else { return }
searchFeed(with: searchTextString.replacingOccurrences(of: " ", with: "+").lowercased(), completion: {_ in
if self.searchResults!.albumMatches.album.count == 0 {
DispatchQueue.main.async {
let alertController = UIAlertController(title: "No Albums Found", message: "Try Another Keyword(s)", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default) { action in
print("Pressed OK")
}
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
} else {
let dataManager = DataManager(data: self.searchResults!)
do {
try dataManager.saveData()
} catch {
print(error)
}
}
})
search(shouldShow: false)
searchBar.resignFirstResponder()
}
}
class SubtitleTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BaseViewController: UITableViewDataSource {
var numberOrSections: Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard section >= 0 && section < numberOrSections else { return 0 }
return model.items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let albumItem = model.items[indexPath.row]
cell.textLabel?.text = albumItem.value(forKeyPath: "name") as? String
cell.detailTextLabel?.text = albumItem.value(forKeyPath: "artist") as? String
cell.accessoryType = .disclosureIndicator
// Populate the cell from the object
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = DetailViewController()
let albumItem = model.items[indexPath.row]
vc.iamgeURL = albumItem.value(forKeyPath: "imageUrl") as? String
vc.albumName = albumItem.value(forKeyPath: "name") as? String
navigationController?.pushViewController(vc, animated: true)
}
}
extension BaseViewController: DataReloadTableViewDelegate {
func reloadAlbumsTable(){
DispatchQueue.main.async {
print(self.model.items.count)
self.tableView.reloadData()
}
}
}
CoreDataModel
import Foundation
import CoreData
class CoreDataModel {
weak var delegate: DataReloadTableViewDelegate?
let coreDataController: CoreDataController
var items:[Albums] = []
init(_ coreDataController: CoreDataController) {
self.coreDataController = coreDataController
self.coreDataController.mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
internal func saveSearchAlbums(responseData: Root) throws {
let newSearch = Searches(context: coreDataController.mainContext)
newSearch.searchQuery = responseData.attr.forField
for (_, element) in responseData.albumMatches.album.enumerated() {
let newAlbum = Albums(context: coreDataController.mainContext)
let artistName = element.artist
let albumName = element.name
let imageUrlTwo = element.image[2].text
let imageUrlZero = element.image[0].text
let imageUrlOne = element.image[1].text
var imageUrl: String = ""
if !JustLetters.blank(text: imageUrlTwo) {
imageUrl = imageUrlTwo
}
if !JustLetters.blank(text: imageUrlZero) {
imageUrl = imageUrlZero
}
if !JustLetters.blank(text: imageUrlOne) {
imageUrl = imageUrlOne
}
if !JustLetters.blank(text: artistName) && !JustLetters.blank(text: albumName) && !JustLetters.blank(text: imageUrl) {
newAlbum.searches = newSearch
newAlbum.artist = artistName
newAlbum.name = albumName
newAlbum.imageUrl = imageUrl
newSearch.addToAlbums(newAlbum)
}
}
// Save context
coreDataController.saveContext()
fetchAlbumsByKeyword(searchTerm: responseData.attr.forField)
}
internal func fetchAlbumsByKeyword(searchTerm: String) {
// Create Fetch Request
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Albums")
// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Add Predicate
let predicate = NSPredicate(format: "name CONTAINS[c] %#", searchTerm)
fetchRequest.predicate = predicate
do {
items = try coreDataController.mainContext.fetch(fetchRequest) as! [Albums]
} catch {
print(error)
}
delegate!.reloadAlbumsTable()
}
internal func fetchAllAlbums() {
// Create the FetchRequest for all searches
let allAlbums: NSFetchRequest = Albums.fetchRequest()
do {
items = try coreDataController.mainContext.fetch(allAlbums)
} catch {
print(error)
}
}
}
Delegate is assigned/set on the class name and not on any instance identifier so a delegate can only be set on a class with one instance
I am unable to show specific proof, I rely on cause and effect of a single change to make the above statement.
I had more than one instance of CoreDataModel, I set the delegate on the first instance in the viewDidLoad, the second instance is set on the search click. I refactored out the DataManager Class which itself creates and instance of CoreDataModel.
The final result is the delegate is not nil and performs as expected. Repo 'show_album_search_results' branch

What i should change in my code to access all the contacts instead of just 1 contact with swift?

I have a really specific problem. I want to access and present to a table view the user's contact list. The problem is that when I run the code I access only one contact from the contact list. I'm trying to think what I should change but nothing so far. Given the code below what I need to change the achieve the result that I want? Here is the code:
import UIKit
import Contacts
import AddressBook
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
var objects = [CNContact]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let addExisting = UIBarButtonItem(title: "Add Existing", style: .Plain, target: self, action: #selector(MasterViewController.addExistingContact))
self.navigationItem.leftBarButtonItem = addExisting
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MasterViewController.insertNewObject(_:)), name: "addNewContact", object: nil)
self.getContacts()
}
func getContacts() {
let store = CNContactStore()
if CNContactStore.authorizationStatusForEntityType(.Contacts) == .NotDetermined {
store.requestAccessForEntityType(.Contacts, completionHandler: { (authorized: Bool, error: NSError?) -> Void in
if authorized {
self.retrieveContactsWithStore(store)
}
})
} else if CNContactStore.authorizationStatusForEntityType(.Contacts) == .Authorized {
self.retrieveContactsWithStore(store)
}
}
func retrieveContactsWithStore(store: CNContactStore) {
do {
let groups = try store.groupsMatchingPredicate(nil)
let predicate = CNContact.predicateForContactsInGroupWithIdentifier(groups[0].identifier)
//let predicate = CNContact.predicateForContactsMatchingName("John")
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactEmailAddressesKey]
let contacts = try store.unifiedContactsMatchingPredicate(predicate, keysToFetch: keysToFetch)
self.objects = contacts
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} catch {
print(error)
}
}
func addExistingContact() {
}
override func viewWillAppear(animated: Bool) {
self.clearsSelectionOnViewWillAppear = self.splitViewController!.collapsed
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func insertNewObject(sender: NSNotification) {
if let contact = sender.userInfo?["contactToAdd"] as? CNContact {
objects.insert(contact, atIndex: 0)
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
// MARK: - Segues
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let object = objects[indexPath.row]
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.contactItem = object
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let contact = self.objects[indexPath.row]
let formatter = CNContactFormatter()
cell.textLabel?.text = formatter.stringFromContact(contact)
cell.detailTextLabel?.text = contact.emailAddresses.first?.value as? String
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return false
}
}
This is how I currently get a list of the user's contacts and store them in a CNContact array.
let request = CNContactFetchRequest(keysToFetch: [CNContactEmailAddressesKey, CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName)])
var contacts = [CNContact]()
do {
try store.enumerateContactsWithFetchRequest(request) { contact, stop in
contacts.append(contact)
}
self.objects = contacts
NSOperationQueue.mainQueue().addOperationWithBlock({
self.tableView.reloadData()
})
} catch {
print(error)
}
I'm assuming your problem is with the line groupsMatchingPredicate(nil), where you don't pass in a predicate so it can't find any matches. Just a thought.

Resources