How to retain updated data in UITableView when back button is Tapped - ios

I am creating an Event App, wherein the list of participants fetch from API are listed in an UITableView. When the user tapped the checkInOutbutton its either, it will check in and register for the event or check out after the event. In every after the user checkin or checkout from the event, the user should pull to refresh to update the data inside the table. I used the codes below to update the data.
var refresher: UIRefreshControl!
override func viewDidLoad() {
super.viewDidLoad()
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "Pull to refresh")
refresher.addTarget(self, action: #selector(ParticipantsViewController.refresh), for: UIControlEvents.valueChanged)
ParticipantTableView.addSubview(refresher)
}
//MARK: FUNCTIONS
#objc func refresh() {
refresher.endRefreshing()
getParticipants()
ParticipantTableView.reloadData()
_ = SCLAlertView(appearance: Appearance).showSuccess("Success", subTitle: "Participants Updated!")
}
I successfully updated the table once I used pull to refresh. The issue is, when I tapped the back button going to my dashboard and tapped the button going back to the VC of my UItableview which is participantViewController, the data is not updated. It goes back to its original data. How can I retain the updated data even if I tapped the back button and goes back to theparticipantsorregistered participants` VC?. Hope you can help me.
original data, gray bgcolor and NOT REGISTERED label(checkin and checkout button is inside the folding cell
.
data once the user tapped checkin button, the background colour turned to green and label is REGISTERED
Dashboard when back button is tapped
Back button when tapped
#IBAction func backbutton(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
ParticipantViewController
import UIKit
import FoldingCell
import SCLAlertView
import AASquaresLoading
class ParticipantsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var ParticipantTableView: UITableView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var countLabel: UILabel!
#IBOutlet weak var notifImageView: UIImageView!
var refresher: UIRefreshControl!
var validPincode: String!
var titleString: String!
var participants: [Attendee]!
var filteredParticipants = [Attendee]()
let kCloseCellHeight: CGFloat = 122
let kOpenCellHeight: CGFloat = 475
var cellHeights = [CGFloat]()
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
createCellHeightsArray()
configureSearchBar()
configureAALoading()
countNotif()
titleLabel.text = self.titleString
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "Pull to refresh")
refresher.addTarget(self, action: #selector(ParticipantsViewController.refresh), for: UIControlEvents.valueChanged)
ParticipantTableView.addSubview(refresher)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
self.ParticipantTableView.reloadData()
}
#IBAction func backbutton(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
//MARK: FUNCTIONS
#objc func refresh() {
refresher.endRefreshing()
getParticipants()
ParticipantTableView.reloadData()
_ = SCLAlertView(appearance: Appearance).showSuccess("Success", subTitle: "Participants Updated!")
}
func configureAALoading() {
self.view.squareLoading.color = UIColor(red: 80/255.0, green: 187/255.0, blue: 113/255.0, alpha: 1.0)
self.view.squareLoading.backgroundColor = UIColor(red: 0/255.0, green: 0/255.0, blue: 0/255.0, alpha: 0.7)
}
func getParticipants() {
var participantType: ParticipantType!
if self.titleString == "PARTICIPANTS" {
participantType = .all
}else {
participantType = .active
}
self.view.squareLoading.start(0.0)
let api = APIService()
api.getParticipants(enteredPincode: validPincode, participantType: participantType, successBlock: { (attendees) in
self.participants = attendees
self.view.squareLoading.stop(0.0)
if self.searchController.isActive && self.searchController.searchBar.text != "" {
self.filterContentForSearchText(searchText: self.searchController.searchBar.text!)
}else {
self.ParticipantTableView.reloadData()
}
}) { (error) in
// Hide loading view
self.view.squareLoading.stop(0.0)
_ = SCLAlertView(appearance: Appearance).showError("Network Error", subTitle: "\(error)")
}
}
func countNotif(){
if participants.count == 0 {
countLabel.isHidden = true
notifImageView.isHidden = true
}else {
countLabel.text = "\(participants.count)"
notifImageView.image = #imageLiteral(resourceName: "participant_notif")
}
}
func createCellHeightsArray() {
cellHeights.removeAll()
if searchController.isActive && searchController.searchBar.text != "" {
for _ in 0...filteredParticipants.count {
cellHeights.append(kCloseCellHeight)
}
}else {
for _ in 0...participants.count {
cellHeights.append(kCloseCellHeight)
}
}
}
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredParticipants = participants.filter { participants in
return participants.displayName.lowercased().contains(searchText.lowercased()) || (participants.department.lowercased().contains(searchText.lowercased()))
// || (participants.employeeNumber?.contains(searchText))! ||
}
createCellHeightsArray()
ParticipantTableView.reloadData()
}
func configureSearchBar() {
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
searchController.searchBar.textColor = UIColor.white
searchController.searchBar.placeholder = "Search by name, department, and employee number"
searchController.searchBar.searchBarStyle = .minimal
searchController.searchBar.barTintColor = UIColor(red: 26/255.0, green: 99/255, blue: 42/255, alpha: 1.0)
searchController.searchBar.tintColor = UIColor.white
searchController.searchBar.backgroundColor = UIColor(red: 26/255.0, green: 99/255, blue: 42/255, alpha: 1.0)
searchController.searchBar.isTranslucent = false
self.ParticipantTableView.tableHeaderView = searchController.searchBar
}
// MARK: TABLE VIEW DATA SOURCE
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.isActive && searchController.searchBar.text != "" {
return filteredParticipants.count
}
//print(participants.count)
return (participants.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FoldingCell", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard case let cell as ParticipantCell = cell else {
return
}
cell.backgroundColor = UIColor.clear
if searchController.isActive && searchController.searchBar.text != "" {
cell.participant = filteredParticipants[indexPath.row]
}else {
cell.participant = participants[indexPath.row]
}
if cellHeights[(indexPath as NSIndexPath).row] == kCloseCellHeight {
cell.unfold(false, animated: false, completion: nil)
} else {
cell.unfold(true, animated: false, completion: nil)
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[(indexPath as NSIndexPath).row]
}
// MARK: TABLE VIEW DELEGATE
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! FoldingCell
if cell.isAnimating() {
return
}
var duration = 0.0
if cellHeights[(indexPath as NSIndexPath).row] == kCloseCellHeight { // open cell
cellHeights[(indexPath as NSIndexPath).row] = kOpenCellHeight
cell.unfold(true, animated: true, completion: nil)
duration = 0.3
} else {// close cell
cellHeights[(indexPath as NSIndexPath).row] = kCloseCellHeight
cell.unfold(false, animated: true, completion: nil)
duration = 0.5
}
UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: { () -> Void in
tableView.beginUpdates()
tableView.endUpdates()
}, completion: nil)
}
}
extension ParticipantsViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!)
}
}
extension UISearchBar {
var textColor: UIColor? {
get {
if let textField = self.value(forKey: "searchField") as? UITextField {
return textField.textColor
}else {
return nil
}
}
set (newValue) {
if let textField = self.value(forKey: "searchField") as? UITextField {
textField.textColor = newValue
textField.font = UIFont(name: "HelveticaNeue", size: 20.0)
}
}
}
}

Your explanation of the problem is kind of confusing to me but I think I understand what you're saying. My interpretation: You present a the view controller for a tableView and refresh the table view with some new data. Then when you dismiss the view controller and go back to it, the new data is gone and you just see the old data.
Well if that's your problem, then the solution is pretty simple. When you return to the view controller, you're probably just creating a completely new viewController and presenting it... which isn't gonna have the new data that was fetched from the network.
In that case you would have to store that original viewController instance in a variable and present THAT, instead of creating an entirely new one.
Edit:
somewhere in your code for the dashboard, you probably have this line
self.present(ParticipantsViewController(), animated: false)
...or some variant of that. Instead of this, do something along the lines of...
class DashboardViewController: UIViewController{
var participantsViewController = ParticipantsViewController()
func responseToSomeUserAction(){
self.present(self.participantsViewController, animated: true)
}
}
In the code above, the same viewController instance will be presented every time the user wants to display the table view, therefore all the data will still be there.

Related

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.)

UISearchController cancel button dismisses UITabBarController when tapped multiple times in a row

I use a custom search controller that has a tableView for showing the results,
the problem is when tapping the cancel button multiple times it dismisses the tabBarController.
But if i press it normally it acts as it should be.
class UICustomSearchController: UISearchController {
private var suggestedTableView: UITableView!
weak var suggestionDelegate: SearchSuggestionsDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
suggestedTableView = UITableView(frame: CGRect(x: 0, y: searchBar.frame.maxY,
width: view.frame.width,
height: view.frame.height - 70))
suggestedTableView.backgroundColor = UIColor.clear
suggestedTableView.tableFooterView = UIView()
view.subviews.forEach {
if $0.isKind(of: UITableView.self) {
$0.removeFromSuperview()
}
}
view.addSubview(suggestedTableView)
suggestedTableView.dataSource = self
suggestedTableView.delegate = self
suggestedTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
let tap = UITapGestureRecognizer(target: self, action: #selector(tableTapped))
suggestedTableView.addGestureRecognizer(tap)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
suggestedTableView.removeFromSuperview()
suggestedTableView = nil
dismiss(animated: false, completion: nil)
}
func reloadSuggestions() {
suggestedTableView.reloadData()
}
private func dismissView() {
searchBar.text = ""
suggestedTableView.removeFromSuperview()
dismiss(animated: false, completion: nil)
suggestionDelegate?.didDismissed()
}
// MARK: - Actions
#objc func tableTapped(tap:UITapGestureRecognizer) {
suggestedTableView.removeGestureRecognizer(tap)
let location = tap.location(in: suggestedTableView)
let path = suggestedTableView.indexPathForRow(at: location)
if let indexPathForRow = path {
tableView(suggestedTableView, didSelectRowAt: indexPathForRow)
} else {
dismissView()
}
}
}
extension UICustomSearchController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return suggestionDelegate?.suggestions().count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let list = suggestionDelegate?.suggestions() ?? []
cell.textLabel?.text = list[indexPath.row]
cell.textLabel?.textColor = UIColor.color(from: .blueTabBar)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let list = suggestionDelegate?.suggestions() ?? []
searchBar.text = list[indexPath.row]
searchBar.becomeFirstResponder()
suggestionDelegate?.didSelect(suggestion: list[indexPath.row])
}
}
And in the viewController that has search:
func initSearchViews() {
// Create the search controller and specify that it should present its results in this same view
searchController = UICustomSearchController(searchResultsController: nil)
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.barTintColor = UIColor.white
searchController.searchBar.tintColor = UIColor.color(from: .blueTabBar)
searchController.searchBar.setValue(Strings.cancel.localized, forKey:"_cancelButtonText")
searchController.suggestionDelegate = self
if let searchTextField = searchController!.searchBar.value(forKey: "searchField") as? UITextField {
searchTextField.placeholder = Strings.search.localized
}
// Make this class the delegate and present the search
searchController.searchBar.delegate = self
}
I tried putting this line in viewController but nothing happened:
definesPresentationContext = true

Swift search bar(controller) memory leaks

I have main screen, with a button on which I segue to searchVC screen.I have a navigation controller between them, in searchVC there are searchController and searchBar.
Problem:I need to activate search when screen appears, but searchBar activation(tap or becomeFirstResponder() ) causes memory leaks(image below)
I tried to remove delegates and the problem disappears, but I need to know when cancel button pressed to segue/dismiss to mainVC
Code:tableView for results, resultView with label for empty results
class SearchViewController: UIViewController,UISearchBarDelegate,UISearchControllerDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var resultView: ResultView!
let searchController = UISearchController(searchResultsController: nil)
var filteredSongs = [SongListModel]()
var songs = SongListModel.fetchSongs()
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search songs"
if #available(iOS 11.0, *) {
navigationItem.titleView = searchController.searchBar
navigationItem.hidesSearchBarWhenScrolling = false
// navigationController?.navigationBar.topItem?.searchController = searchController
// navigationItem.titleView?.isHidden = true
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
} else {
tableView.tableHeaderView = searchController.searchBar
}
searchController.searchBar.showsCancelButton = true
searchController.definesPresentationContext = true
searchController.searchBar.sizeToFit()
searchController.delegate = self
searchController.searchBar.delegate = self
tableView.keyboardDismissMode = .interactive
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
delay(0.1) { [unowned self] in
self.searchController.searchBar.becomeFirstResponder()
}
}
func delay(_ delay: Double, closure: #escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchController.searchBar.resignFirstResponder()
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredSongs = songs.filter({( song : SongListModel) -> Bool in
return song.musicFileName.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchController.searchBar.endEditing(true)
searchController.searchBar.resignFirstResponder()
// searchController.searchBar.delegate = nil
// searchController.searchResultsUpdater = nil
dismiss(animated: true, completion: nil)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PlayTilesSegue", let destinationVC = segue.destination as? TilesViewController, let selectedIndex = tableView.indexPathForSelectedRow?.row {
let song: SongListModel
if isFiltering() {
song = filteredSongs[selectedIndex]
} else {
song = songs[selectedIndex]
}
destinationVC.songFileName = song.musicFileName
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
}
extension SearchViewController: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
resultView.setIsFilteringToShow(filteredItemCount: filteredSongs.count, of: songs.count)
return filteredSongs.count
}
resultView.setNotFiltering()
return songs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "filteredCell", for: indexPath) as! FilteredSongCell
let song: SongListModel
if isFiltering() {
song = filteredSongs[indexPath.row]
} else {
song = songs[indexPath.row]
}
cell.listenSongButton.setBackgroundImage(UIImage(named: "playback"), for: .normal)
cell.filteredAuthorNameLabel.text = song.authorName
cell.filteredSongNameLabel.text = song.songName
cell.playGameButton.setTitle(song.playButton.rawValue, for: .normal)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "PlayTilesSegue", sender: indexPath)
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension SearchViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
Image memory leaks
Deactivating the search controller on removing VC from parent helps to avoid memory leak:
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil, searchController.isActive {
searchController.isActive = false
}
}
You need to set the UISearchController searchBar's delegate. Once you have done this, the addition of the delegate method searchBarCancelButtonClicked: will properly be called.
Here it is.

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`

Swift - Segue to wrong index when UISearchBar is active

I am new to code, and I just can't find the solution to this problem. Most of the answers on the internet are deprecated or unanswered.
I want to search something into my searchbar, after that I want to press on one of the results. When I click on the result I want it to give me the corresponding information in another viewcontroller.
Here's my problem: When I click on a result, the information in the other viewcontroller doesn't correspond with the information that the result gave me. What he does gives me is the information that corresponds to the tableview. So, it segues with a wrong index. Does anyone knows what the solution is?
Here's my code (tableviewcontroller with searchbar):
var myIndex = 0
extension UIColor { //themakleur
static let candyGreen = UIColor(red: 71.0/255.0, green: 81.0/255.0, blue:
89.0/255.0, alpha: 1.0)}
var watjeberekend = ["Omtrek Cirkel","Oppervlakte Cirkel","Lengte
Boog","Oppervlakte Sector","Oppervlakte Bol"]
class scheikunde: UITableViewController, UISearchResultsUpdating {
var scheikundeformules = ["Omtrek Cirkel","Oppervlakte Cirkel","Lengte Boog","Oppervlakte Sector","Oppervlakte Bol"]
var searchcontroller : UISearchController!
var filterednamen = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.searchcontroller = UISearchController(searchResultsController: nil)
self.tableView.tableHeaderView = self.searchcontroller.searchBar
self.searchcontroller.searchResultsUpdater = self
self.searchcontroller.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
self.navigationItem.hidesBackButton = false;
self.navigationController?.isNavigationBarHidden = false
//navigationcontroller layout
navigationController?.navigationBar.barTintColor = UIColor(red: 71.0/255.0, green: 81.0/255.0, blue: 89.0/255.0, alpha: 1.0)
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
navigationController?.navigationBar.layer.borderColor = UIColor.candyGreen.cgColor
navigationController?.navigationBar.layer.borderWidth = 0
//searchcontroller layout
searchcontroller.searchBar.layer.borderWidth = 0
searchcontroller.searchBar.layer.borderColor = UIColor.candyGreen.cgColor
UISearchBar.appearance().barTintColor = .candyGreen
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).tintColor = .candyGreen
searchcontroller.searchBar.backgroundImage = UIImage(named: "themakleur.jpg")
let cancelButtonAttributes: [String: AnyObject] = [NSForegroundColorAttributeName: UIColor.white]
UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self]).setTitleTextAttributes(cancelButtonAttributes, for: .normal)
}
func updateSearchResults(for searchController: UISearchController) {
self.filterednamen = self.scheikundeformules.filter { (naam : String) -> Bool in
if naam.lowercased().contains(self.searchcontroller.searchBar.text!.lowercased()) {
return true
}else{
return false}}
self.tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if !searchcontroller.isActive || searchcontroller.searchBar.text == "" {
return self.scheikundeformules.count
}else {
return self.filterednamen.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Custommath
cell.label.text = scheikundeformules[indexPath.row]
if !searchcontroller.isActive || searchcontroller.searchBar.text == "" {
//cell.textLabel?.text = self.namen[indexPath.row]
cell.label.text = self.scheikundeformules[indexPath.row]
}else{
//cell.textLabel?.text = self.filterednamen[indexPath.row]
cell.label.text = self.filterednamen[indexPath.row]
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex = indexPath.row
performSegue(withIdentifier: "segue", sender: self)
}
}
Here is my code from the viewcontroller:
import UIKit
class scheikundeformule: UIViewController{
#IBOutlet var uitleg6: UILabel!
#IBOutlet var formuleomschrijving: UILabel!
#IBOutlet var uitleg5: UILabel!
#IBOutlet var uitleg4: UILabel!
#IBOutlet var uitleg3: UILabel!
#IBOutlet var uitleg2: UILabel!
#IBOutlet var uitleg1: UILabel!
#IBOutlet var titlelabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
titlelabel.text = scheikundeformules[myIndex]
uitleg1.text = uitlegformules2[myIndex]
uitleg2.text = uitlegformules3[myIndex]
uitleg3.text = uitlegformules4[myIndex]
uitleg4.text = uitlegformules5[myIndex]
uitleg5.text = uitlegformules6[myIndex]
uitleg6.text = uitlegformules7[myIndex]
self.navigationItem.hidesBackButton = false;
self.navigationController?.isNavigationBarHidden = false
formuleomschrijving.text = watjeberekend[myIndex]
//keyboard weg deel 1
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(startscreen.dismissKeyboard))
//Uncomment the line below if you want the tap not not interfere and cancel other interactions.
//tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
Where is your function prepare for segue ? if you want to pass the data to the next view controller you need to send it in the fun prepare for segue, and remember that the indexpath.row needs to be used with the array that is the current datasource of the tableview, the solution will be something like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVC = segue.destination as? YourViewController {
if !searchcontroller.isActive {
destinationVC.name = filterednamen[myIndex]
} else {
destinationVC.name = scheikundeformules[myIndex]
} }
}
Add identifier for scheikundeformule view controller in storyboard like below :
Step 1:
Step 2:
change your tableview didSelectRow like below :
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex = indexPath.row
let scheikundeformuleController = self.storyboard?.instantiateViewController(withIdentifier: "scheikundeformule") as! scheikundeformule
scheikundeformuleController.myIndex = indexPath.row
self.present(scheikundeformuleController, animated: true, completion: nil)
}
Step 3:
You have to add var myIndex = 0 to your scheikundeformule controller. Like below :
class scheikundeformule: UIViewController{
var myIndex = 0
........Your code
}

Resources