Access correct indexPath Row for user - ios

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

Related

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

How to remove extra space from table view of style- grouped?

Yes I have already tried these from other similar questions but they didn't work:
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
tableView.tableHeaderView = UIView(frame: frame)
and
self.automaticallyAdjustsScrollViewInsets = false;
definesPresentationContext = true
Have a look at the extra space I am trying to remove below search bar:
Screenshot
Maybe you can point out an improvement in my code for the same:
import UIKit
class SelectCountryViewController: UITableViewController, UISearchBarDelegate, UISearchResultsUpdating {
struct CellStruct
{
var countryName : String
var countryFlag : String
var countryDialCode : String
}
var cellDatas = [CellStruct]()
var filteredCellDatas = [CellStruct]()
var searchController : UISearchController!
var resultsController = UITableViewController()
var refreshController = UIRefreshControl()
var searchTextField : UITextField!
var searchLoaded = false
var isSearching = false
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self;
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
tableView.tableHeaderView = UIView(frame: frame)
self.automaticallyAdjustsScrollViewInsets = false;
//tableView.separatorStyle = UITableViewCellSeparatorStyle.singleLine
definesPresentationContext = true
configureSearchController()
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(cancel))
navigationItem.leftBarButtonItem = cancelButton
//self.navigationItem.title = "Select Country"
let searchButton: UIBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: self, action: #selector(searchButtonAction))
searchButton.image = UIImage(named: "search")
self.navigationItem.rightBarButtonItem = searchButton
refreshController.attributedTitle = NSAttributedString(string: "")
refreshController.addTarget(self, action: #selector(refreshSelector), for: .valueChanged)
tableView.addSubview(refreshController)
guard let path = Bundle.main.path(forResource: "countries", ofType: "json")
else
{
return
}
let url = URL(fileURLWithPath: path)
do
{
let data = try Data (contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
print(json)
guard let array = json as? [Any] else { return}
for info in array {
guard let userDict = info as? [String: Any] else { return}
guard let code = userDict["code"] as? String else { print("No code found"); return}
guard let dialCode = userDict["dial_code"] as? String else { print("No dial code found"); return}
guard let name = userDict["name"] as? String else { print("No name found"); return}
print("We have: ", code, dialCode, name)
cellDatas.append(CellStruct(countryName: name, countryFlag: code, countryDialCode: dialCode))
}
}
catch
{
print(error)
}
}
func configureSearchController()
{
resultsController.tableView.delegate = self
resultsController.tableView.dataSource = self
self.searchController = UISearchController(searchResultsController: self.resultsController)
//self.tableView.tableHeaderView = self.searchController.searchBar
self.searchController.searchResultsUpdater = self
self.searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.delegate = self
self.searchController.searchBar.scopeButtonTitles = []
for subView in searchController.searchBar.subviews {
for subViewOne in subView.subviews {
if subViewOne is UITextField {
searchTextField = subViewOne as! UITextField
subViewOne.backgroundColor = UIColor.white
break
}
}
}
self.automaticallyAdjustsScrollViewInsets = false;
extendedLayoutIncludesOpaqueBars = true
definesPresentationContext = true
}
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
// tableView.setContentOffset(self.navigationItem, animated: true)
searchController.searchBar.barTintColor = UIColor.white
//searchController.searchBar.layer.borderColor = UIColor.white.cgColor
searchTextField.backgroundColor = UIColor.searchBarTextFieldGrey()
return true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
self.searchController.searchBar.showsCancelButton = false
// searchController.searchBar.barTintColor = nil
searchTextField.backgroundColor = UIColor.white
searchController.searchBar.barTintColor = nil
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.navigationBar.shadowImage = nil
self.navigationController?.navigationBar.backIndicatorImage = nil
}
func updateSearchResults(for searchController: UISearchController) {
//tableView.separatorStyle = UITableViewCellSeparatorStyle.none
if searchController.searchBar.text! == "" {
filteredCellDatas = cellDatas
} else {
// Filter the results
filteredCellDatas = cellDatas.filter { $0.countryName.lowercased().contains(searchController.searchBar.text!.lowercased()) }
}
resultsController.tableView.reloadData()
// tableView.separatorStyle = .none
// tableView.separatorStyle = UITableViewCellSeparatorStyle.singleLine
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == resultsController.tableView
{
isSearching = true
return filteredCellDatas.count
}
else
{
isSearching = false
return cellDatas.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
cell?.separatorInset.left = 15
if cell == nil {
cell = UITableViewCell(style: .value1, reuseIdentifier: "Cell")
cell?.separatorInset.left = 0
}
if tableView == resultsController.tableView
{
cell?.textLabel?.text = filteredCellDatas[indexPath.row].countryName
cell?.detailTextLabel?.text = filteredCellDatas[indexPath.row].countryDialCode
cell?.imageView?.image = UIImage (named: filteredCellDatas[indexPath.row].countryFlag)
}
else
{
cell?.textLabel?.text = cellDatas[indexPath.row].countryName
cell?.detailTextLabel?.text = cellDatas[indexPath.row].countryDialCode
cell?.imageView?.image = UIImage (named: cellDatas[indexPath.row].countryFlag)
}
cell?.textLabel?.textColor = UIColor.labelGray2()
cell?.detailTextLabel?.textColor = UIColor.labelGray2()
cell?.textLabel?.font = UIFont(name:"SF Pro Text", size:15)
cell?.detailTextLabel?.font = UIFont(name:"SF Pro Text", size:15)
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row, indexPath.section)
// let currentCell = tableView.cellForRow(at: indexPath)
if(isSearching)
{
print(filteredCellDatas[indexPath.row].countryFlag)
UserDefaults.standard.set(filteredCellDatas[indexPath.row].countryFlag, forKey: "preferredCountry")
if searchController.isActive {
DispatchQueue.main.async {
self.searchController.dismiss(animated: true, completion: {
self.performSegue(withIdentifier: "unWindFromSelectCountry", sender: nil)
})
}
} else {
// Play segue, dismiss or pop ...
self.performSegue(withIdentifier: "unWindFromSelectCountry", sender: nil)
}
}
else
{
print(cellDatas[indexPath.row].countryFlag)
UserDefaults.standard.set(cellDatas[indexPath.row].countryFlag, forKey: "preferredCountry")
self.performSegue(withIdentifier: "unWindFromSelectCountry", sender: nil)
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 {
return CGFloat.leastNormalMagnitude
}
return tableView.sectionHeaderHeight
}
#objc func cancel(){
navigationController?.popViewController(animated: true)
}
#objc func refreshSelector()
{
if(!searchLoaded)
{
searchLoaded = true
self.tableView.tableHeaderView = searchController.searchBar
print( "Got ya")
}
refreshController.endRefreshing()
}
#objc func searchButtonAction() {
if(!searchLoaded)
{
searchLoaded = true
self.tableView.tableHeaderView = searchController.searchBar
// self.navigationItem.titleView = searchController.searchBar
}
self.searchController.searchBar.becomeFirstResponder()
self.searchController.searchBar.text = ""
// self.navigationItem.rightBarButtonItem = nil
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
As said the table view is of style grouped. I am configuring search bar from code. And updating results therefore using code in the same table view.
Thanks in advance
Maybe it is not a tableHeaderView I presume that it could be particular Section HeaderView try with this:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 {
return CGFloat.leastNormalMagnitude
}
return tableView.sectionHeaderHeight
}
Code is hiding header for first section in the table

index out of range UISearchController

I am trying to obtain the behaviour you find in whatsapp when trying to Add Participants to a group. In a tableView I have implemented UISearchController.
When a new row is selected in tableView, it is assigned an filled image(see screenshot below) and it is added/deleted to/from selectedUsers. When I start searching in the searchBar cells get rearranged and I get index out of range error, which is normal. I don't know where to go from here.
Scope:
allow the user to add/remove users whether searchBar is active or not
make sure there are no duplicate users in selectedUsers. This will be saved to DataBase.
remove cancel button that is attached on searchBar.
What have I tried?
class GroupRegistrationViewController: UITableViewController {
let searchController = UISearchController(searchResultsController: nil)
var usersArray = [User]() //users that currentUser is following
var currentUser: User!
var selectedUsers = [User]() //selected users in this controller, selected users we follow
var filteredUsers = [User]() //this property will hold the Users that the currentUser is searching for
override func viewDidLoad() {
super.viewDidLoad()
self.fetchUsers()
navigationItem.title = "GroupRegistration"
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
searchController.searchBar.placeholder = "Search Members"
searchController.hidesNavigationBarDuringPresentation = false
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//simply check whether the user is searching or not
if isFiltering() {
return filteredUsers.count
}
return usersArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GroupRegistrationTableViewCell") as! GroupRegistrationTableViewCell
if isFiltering() {
cell.user = filteredUsers[indexPath.row]
}else{
cell.user = usersArray[indexPath.row]
}
if selectedUsers.contains(cell.user) {
//User type conforms to Equatable protocol so check is allowed
cell.added = true
}
return cell
}
//this method determines if you are currently filtering results or not
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func searchBarIsEmpty() -> Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! GroupRegistrationTableViewCell
cell.added = !cell.added
if cell.added == true {
self.addRecipient(user: cell.user)
}else{
let index = selectedUsers.index(of: cell.user)!
self.deleteRecipient(user: cell.user, index: index)
}
}
// - Helper methods
// - add
// - delete
func addRecipient(user: User) {
self.selectedUsers.append(user)
}
func deleteRecipient(user: User, index: Int) {
selectedUsers.remove(at: index)
}
}//end of class
extension GroupRegistrationViewController {
//get users from firebase
func fetchUsers() {
DatabaseReference.users(uid: currentUser.userUID).reference().child("follows").observe(.childAdded, with: { (snapshot) in
let user = User(dictionary: snapshot.value as! [String:Any])
self.usersArray.insert(user, at: 0)
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.insertRows(at: [indexPath], with: .fade)
})
}
}
//UISearchResultsUpdating Delegate
extension GroupRegistrationViewController: UISearchResultsUpdating {
func filteredContentForSearchText(searchText: String, scope: String = "All") {
filteredUsers = usersArray.filter({ (user) -> Bool in
return user.fullName.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
/*
whenever the user adds or removes text in the search bar, the UISearchController will inform the GroupRegistrationViewController class of the change via a call to updateSearchResults(for:), which in turn calls filterContentForSearchText(_:scope:).
*/
func updateSearchResults(for searchController: UISearchController) {
filteredContentForSearchText(searchText: searchController.searchBar.text!)
searchController.searchBar.showsCancelButton = false
//CancelButton shows up when I tap for the first time in the search bar. !!!! - not desired behaviour.
//CancelButton disapears when I start typing
}
}
`class GroupRegistrationTableViewCell: UITableViewCell {`
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var displayNameLabel: UILabel!
#IBOutlet weak var checkboxImageView: UIImageView!
#IBOutlet weak var fullNameTextLabel: UILabel!
var user: User! {
didSet {
self.updateUI()
}
}
//when a row is selected added property is assigned true/false value
var added: Bool = false {
didSet{
if added == false {
checkboxImageView.image = UIImage(named: "icon-checkbox")
}else {
checkboxImageView.image = UIImage(named: "icon-checkbox-filled")
}
}
}
//add cache here
var cache = SAMCache.shared()
func updateUI() {
let profilePictureKey = "\(user.userUID)-profilePicture"
if let image = cache?.image(forKey: profilePictureKey){
self.profileImageView.image = image
self.profileImageView.layer.cornerRadius = self.profileImageView.bounds.width / 2.0
self.profileImageView.layer.masksToBounds = true
} else {
user.downloadProfilePicture { (image, error) in
self.cache?.setImage(image, forKey: profilePictureKey)
self.profileImageView.image = image
self.profileImageView.layer.cornerRadius = self.profileImageView.bounds.width / 2.0
self.profileImageView.layer.masksToBounds = true
}
}
displayNameLabel.text = user.username
checkboxImageView.image = UIImage(named: "icon-checkbox")
fullNameTextLabel.text = user.fullName
}
`}//end of class`

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()
}
}

Value in JSON does not display in tableviewcell custom

Can't display data in TableViewCell.Data reports of events, but the when you open the array "sports" display the data in cels no.The display of the title occurs and the transfer is ended...
This is my json code...
Event.swift
import UIKit
struct Event {
let match : String
let forecast : String
let data : String
let image : UIImage
var sports : [Sport]
init (match : String, forecast : String, data: String, image : UIImage, sports : [Sport]) {
self.match = match
self.forecast = forecast
self.data = data
self.image = image
self.sports = sports
}
static func eventsFromBundle ()-> [Event] {
var events = [Event] ()
guard let url = Bundle.main.url(forResource: "events", withExtension: "json") else {
return events
}
do {
let data = try Data(contentsOf: url)
guard let rootObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String : Any] else {
return events
}
guard let eventObjects = rootObject["events"] as? [[String: AnyObject]] else {
return events
}
for eventObject in eventObjects {
if let match = eventObject["match"] as? String,
let forecast = eventObject["forecast"] as? String,
let data = eventObject["data"] as? String,
let imageName = eventObject["image"] as? String,
let image = UIImage(named: imageName),
let sportsObject = eventObject["sports"] as? [[String : String]]{
var sports = [Sport]()
for sportObject in sportsObject {
if let nameTitle = sportObject["name"] ,
let titleName = sportObject["image"],
let titleImage = UIImage(named: titleName + ".jpg"),
let prognozLabel = sportObject["prognoz"],
let obzor = sportObject["obzor"] {
sports.append(Sport(name: nameTitle, prognoz: prognozLabel, image: titleImage, obzor: obzor, isExpanded: false))
}
}
let event = Event(match: match, forecast: forecast, data: data, image: image, sports: sports)
events.append(event)
}
}
} catch {
return events
}
return events
}
}
import UIKit
class SportViewController: BaseViewController {
var events = Event.eventsFromBundle ()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(forName: .UIContentSizeCategoryDidChange, object: .none, queue: OperationQueue.main) { [weak self] _ in
self?.tableView.reloadData()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? SportDetailViewController,
let indexPath = tableView.indexPathForSelectedRow {
destination.selectedEvent = events[indexPath.row]
}
}
}
extension SportViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellMatch", for: indexPath) as! SportTableViewCell
let event = events[indexPath.row]
cell.matchLabel.text = event.match
cell.imageMatch.image = event.image
cell.forecastLabel.text = event.forecast
cell.dataLabel.text = event.data
cell.matchLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
cell.forecastLabel.font = UIFont.preferredFont(forTextStyle: .callout)
return cell
}
}
Her is the controller.SportDetailViewController.swift
import UIKit
class SportDetailViewController: UIViewController {
var selectedEvent : Event!
let obzorText = "Select for more info >"
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
title = selectedEvent.match
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 300
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
}
extension SportDetailViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return selectedEvent.sports.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : SportDetailTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellMatch", for: indexPath) as! SportDetailTableViewCell
let sport = selectedEvent.sports[indexPath.row]
cell.nameTitle.text = sport.name
cell.titleImage.image = sport.image
cell.prognozLabel.text = sport.prognoz
cell.selectionStyle = .none
cell.nameTitle.backgroundColor = UIColor.darkGray
cell.backgroundColor = UIColor.red
cell.obzorText.text = sport.isExpanded ? sport.obzor : obzorText
cell.obzorText.textAlignment = sport.isExpanded ? .left : .center
return cell
}
}
extension SportDetailViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? SportDetailTableViewCell else { return }
var sport = selectedEvent.sports[indexPath.row]
sport.isExpanded = !sport.isExpanded
selectedEvent.sports[indexPath.row] = sport
cell.obzorText.text = sport.isExpanded ? sport.obzor : obzorText
cell.obzorText.textAlignment = sport.isExpanded ? .left : .center
tableView.beginUpdates()
tableView.endUpdates()
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
all these methods have tried: tableview.datasource = self , tableview.delegate = self и reloadData().....in viewDidLoad.
Delete this init from your struct: (because struct gets free initializer)
init (match : String, forecast : String, data: String, image : UIImage, sports : [Sport]) {
self.match = match
self.forecast = forecast
self.data = data
self.image = image
self.sports = sports
}
Now, your var events won't be populated as you are calling method in class scope. So change this:
class SportViewController: BaseViewController {
var events = Event.eventsFromBundle ()
...
...
}
to
class SportViewController: BaseViewController {
var events = [Event]()
...
...
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
events = Event().eventsFromBundle()
}
...
...
}
This should solve your problem.

Resources