Developing an iOS application with Xcode ver 9.2, Swift.
When the edit button on the top right of the NavigationBar is pressed, how to change the textField in the TableViewCell to make it editable?
To prevent the TextField from being edited in the initial display, I set textField.isEnabled = false with awakeFromNib() in the TableViewCell.swift.
When the edit button is pressed, I want to set it to true so that I can edit the TextField.
Could you tell me how?
Relationship between object placement and code (in parentheses) is below.
NavigationController - TableViewController (TableViewController.swift) - TableViewCell (TableViewCell.swift) - TextField
Here is the code.
TableViewController.swift
import UIKit
class TableViewController: UITableViewController, TableViewCellDelegate {
#IBOutlet var ttableView: UITableView!
var array:[String] = ["aaa", "bbb", "ccc", "ddd", "eee"]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return array.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "inputCell", for: indexPath) as! TableViewCell
cell.textField.text = array[indexPath.row]
cell.delegate = self
return cell
}
func textFieldDidEndEditing(cell: TableViewCell, value: String) -> () {
let path = tableView.indexPathForRow(at: cell.convert(cell.bounds.origin, to: tableView))
array[(path?.row)!] = value
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
array.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let cell = tableView.cellForRow(at: sourceIndexPath) as! TableViewCell
let moveData = cell.textField.text
array.remove(at: sourceIndexPath.row)
array.insert(moveData!, at: destinationIndexPath.row)
}
}
TableViewCell.swift
import UIKit
protocol TableViewCellDelegate {
func textFieldDidEndEditing(cell: TableViewCell, value: String) -> ()
}
class TableViewCell: UITableViewCell, UITextFieldDelegate {
var delegate: TableViewCellDelegate! = nil
#IBOutlet weak var textField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
textField.returnKeyType = .done
// To prevent the TextField from being edited in the initial display
textField.isEnabled = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.delegate.textFieldDidEndEditing(cell: self, value: textField.text!)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
I added the following from the first time question and answers.
Editing screen shot: after edit button is pressed
If there are many elements of the array, the cells will be outside the screen, but I want to make all textField editable as well.
var array:[String] = ["aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk", "lll", "mmm", "nnn", "ooo", "ppp", "qqq", "rrr", "sss", "ttt"]
Editing screen shot for many elements
Finally resolved code
TableViewController.swift
import UIKit
class TableViewController: UITableViewController, TableViewCellDelegate {
#IBOutlet var ttableView: UITableView!
// var array:[String] = ["aaa", "bbb", "ccc", "ddd", "eee"]
var array:[String] = ["aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk", "lll", "mmm", "nnn", "ooo", "ppp", "qqq", "rrr", "sss", "ttt"]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(rightBarButtonItemTapped))
}
// handle tap by button...
#objc func rightBarButtonItemTapped(_ sender: UIBarButtonItem) {
ttableView.setEditing(!ttableView.isEditing, animated: true)
navigationItem.rightBarButtonItem?.title = ttableView.isEditing ? "Done" : "Edit"
navigationItem.rightBarButtonItem?.style = ttableView.isEditing ? .done : .plain
ttableView.visibleCells.forEach { cell in
guard let cell = cell as? TableViewCell else { return }
cell.textField.isEnabled = ttableView.isEditing
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return array.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "inputCell", for: indexPath) as! TableViewCell
cell.textField.text = array[indexPath.row]
cell.textField.isEnabled = tableView.isEditing
cell.delegate = self
return cell
}
func textFieldDidEndEditing(cell: TableViewCell, value: String) -> () {
let path = tableView.indexPathForRow(at: cell.convert(cell.bounds.origin, to: tableView))
array[(path?.row)!] = value
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
array.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
if tableView.isEditing {
return UITableViewCellEditingStyle.delete
} else {
return UITableViewCellEditingStyle.none
}
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let cell = tableView.cellForRow(at: sourceIndexPath) as! TableViewCell
let moveData = cell.textField.text
array.remove(at: sourceIndexPath.row)
array.insert(moveData!, at: destinationIndexPath.row)
}
}
TableViewCell.swift
import UIKit
protocol TableViewCellDelegate {
func textFieldDidEndEditing(cell: TableViewCell, value: String) -> ()
}
class TableViewCell: UITableViewCell, UITextFieldDelegate {
var delegate: TableViewCellDelegate! = nil
#IBOutlet weak var textField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
textField.returnKeyType = .done
//textField.isEnabled = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.delegate.textFieldDidEndEditing(cell: self, value: textField.text!)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
First, you should handle navigation button tap, find cell(s) with textField and then set textField.isEnabled = true.
You can do something like this:
override func viewDidLoad() {
super.viewDidLoad()
// in your code `self.editButtonItem` is the `UIBarButtonItem`, so make sure that it configured properly
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(rightBarButtonItemTapped))
}
// handle tap by button...
#objc func rightBarButtonItemTapped(_ sender: UIBarButtonItem) {
// and set `textField.isEnabled` to all `visibleCells`
ttableView.visibleCells.forEach { cell in
guard let cell = cell as? TableViewCell { else return }
cell.textField.isEnabled = true
}
// or set `isEnabled` to specific `textField` at index 0
if let cell = ttableView.cellForRow(at: IndexPath(row: 0, section: 0)) {
cell.textField.isEnabled = true
}
}
UPD.
Base on your screenshot you:
doesn't need to set textField.isEnabled = false
you just need setEditing for tableView and show appropriate title for button in navigation bar.
Example:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(rightBarButtonItemTapped))
}
#objc func rightBarButtonItemTapped(_ sender: UIBarButtonItem) {
ttableView.setEditing(!ttableView.isEditing, animated: true)
navigationItem.rightBarButtonItem?.title = ttableView.isEditing ? "Done" : "Edit"
navigationItem.rightBarButtonItem?.style = ttableView.isEditing ? .done : .plain
}
LAST UPD
Ok, now only steps you should do:
remove from awakeFromNib code that disable textField
in cellForRowAtIndexPath method in your viewController write cell.textField.isEnabled = tableView.isEditing
to set tableView in editing mode use my UPD code
to enable all textFields in cells you should use approach from original answer with visibleCells (i updated this part, now you shouldn't have any error). note, that this code apply only for currently visible cells. for others it also works, but set textField enabled part goes in cellForRowAtIndexPath method because these cells will appear on the screen.
you can do so by creating an action of your navigation barbutton item , and in that action you can simply do the textField enabled, as shown below:
#IBAction func editTapped(_ sender: Any) {
print("editTapped")
for i in 0..< ttableView.visibleCells.count{
let cell = ttableView.cellForRow(at: IndexPath(row: i, section: 0)) as! TableViewCell
cell.textField.isEnabled = true
}
}
The problem:
Instead of adding a search bar(with a search and results controller) to a table view controller, I have added it to a regular view controller's navigation bar. At first everything seems fine, but when I click on the search bar the screen turns gray.
This is my code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating, UISearchBarDelegate{
var schools = ["Saratoga", "Fremont", "Argonaut", "Redwood", "Foothill", "Miller", "Rolling Hills"].sorted()
var filteredSchools = ["Saratoga", "Fremont", "Argonaut", "Redwood", "Foothill", "Miller", "Rolling Hills"].sorted()
var searchController: UISearchController!
var resultsController: UITableViewController!
override func viewDidLoad() {
super.viewDidLoad()
resultsController = UITableViewController()
searchController = UISearchController(searchResultsController: resultsController)
resultsController.tableView.delegate = self
resultsController.tableView.dataSource = self
searchController.searchResultsUpdater = self
self.view.addSubview(searchController.searchBar)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: searchController.searchBar)
}
func updateSearchResults(for searchController: UISearchController) {
let currText = searchController.searchBar.text ?? ""
filteredSchools = schools.filter({ (school) -> Bool in
if school.contains(currText){
return true
}
return false
})
resultsController.tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSchools.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = filteredSchools[indexPath.row]
return cell
}
}
Add these lines in viewDidLoad:
resultsController.tableView.backgroundColor = UIColor.clear
searchController.hidesNavigationBarDuringPresentation = false
Your Navigation Bar is hiding that's all.
If you don't want the gray tint:
searchController.dimsBackgroundDuringPresentation = false
I am using a UISearchController in my project. I initiate the search controller by supplying the init(searchResultsController) method with an UIViewController object that manages a tableView. The code for this object is:
import UIKit
class ResultsTableViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var list: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ResultsTableViewController: UITableViewDelegate{}
extension ResultsTableViewController: UITableViewDataSource{
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")
cell?.textLabel?.text = list[indexPath.row]
} else {
cell?.textLabel!.text = list[indexPath.row]
}
return cell!
}
}
Furthermore, when I try to access the resultsTableViewController.tableView from the updateSearchResultsForSearchController(searchController: UISearchController) method of the UISearchResultsUpdating protocol to populate its "list" array, it gives me an error. The tableView returns nil and the app crashes. I would like to point out that I have connected the data source, delegate, and the IBOutlet variable of the tableView to the appropriate view controller. I was hoping for someone to explain to me why this happens? I think I have a misunderstanding in the life cycle of the ResultsTableViewController. Lastly, when I drag a TableViewController from the storyboard instead of making my own table view controller from scratch everything works smoothly without any errors! Can you please help me understand whats going on here?
Edit: The code for my initial view controller is:
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var resultsTableViewController = ResultsTableViewController()
var searchController: UISearchController!
let list = ["Apple", "Orange", "Bananas","Kiwi", "Cucumbers", "Apricot","Peach", "Cherry", "Mangosteen","Strawberry", "Blueberry", "Raspberry","Watermelon", "Persimmon", "plums","Papaya", "Jackfruit", "Lichi"]
var filteredList: [String]!
override func viewDidLoad() {
super.viewDidLoad()
setUpSearchController()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setUpSearchController(){
searchController = UISearchController(searchResultsController: resultsTableViewController)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = true
tableView.tableHeaderView = searchController.searchBar
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: false)
}
}
extension FirstViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell")
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: "cell")
cell?.textLabel?.text = list[indexPath.row]
}else {
cell?.textLabel?.text = list[indexPath.row]
}
return cell!
}
}
extension FirstViewController: UISearchResultsUpdating{
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredList = list.filter({
item in
return item.containsString(searchController.searchBar.text!)
})
resultsTableViewController.list = filteredList
resultsTableViewController.tableView.reloadData()
}
}
var resultsTableViewController = ResultsTableViewController()
creates a new ResultsTableViewController but it isn't linked to your storyboard scene, so none of the #IBOutlets will be set. You need to set an identifier for your scene (say resultsViewController) and then use that to instantiate the view controller from the storyboard:
var resultsTableViewController: ResultsTableViewController!
override func viewDidLoad() {
super.viewDidLoad()
setUpSearchController()
}
func setUpSearchController(){
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
self.resultsTableViewController = storyboard.instantiateViewControllerWithIdentifer("resultsViewController") as! ResultsTableViewController
searchController = UISearchController(searchResultsController: resultsTableViewController)
searchController.dimsBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = true
tableView.tableHeaderView = searchController.searchBar
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: false)
}
While searching a tableView, every time I try to select a row it just takes me back to the unsearched tableView. What am I missing? the segue works perfectly when not filtering through the table. The ability to select a row just disapears while the searchBar is activated.
import UIKit
import Foundation
class BenchmarkWODViewController: UITableViewController, UISearchResultsUpdating {
var WodList = [WOD]()
var FilteredWodList = [WOD]()
var Keyword = ""
var searchController : UISearchController?
var index = Int()
#IBAction func backButton(sender: AnyObject) {
self.navigationController?.popViewControllerAnimated(true)
}
override func viewDidLoad() {
super.viewDidLoad()
for wodData in BenchmarkWODs.library {
let wod = WOD(dictionary: wodData)
WodList.append(wod)
}
// Search Bar
self.searchController = UISearchController(searchResultsController: nil)
self.searchController?.searchBar.autocapitalizationType = .None
self.tableView.tableHeaderView = self.searchController?.searchBar
self.searchController?.searchResultsUpdater = self
self.Keyword = ""
definesPresentationContext = true
self.filterByName()
}
func filterByName(){
self.FilteredWodList = self.WodList.filter({ (wod: WOD) -> Bool in
if self.Keyword.characters.count == 0 {
return true
}
if (wod.name?.lowercaseString.rangeOfString(self.Keyword.lowercaseString)) != nil {
return true
}
return false
})
self.tableView.reloadData()
}
// Search Bar Function
func updateSearchResultsForSearchController(searchController: UISearchController) {
Keyword = searchController.searchBar.text!
self.filterByName()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.FilteredWodList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("BenchmarkCell", forIndexPath: indexPath) as UITableViewCell
let wod = self.FilteredWodList[indexPath.row]
if let wodName = wod.name {
cell.textLabel?.text = wodName
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.filterByName()
self.performSegueWithIdentifier("showBenchmarkDetail", sender: nil)
}
}
Figured it out after playing around. Apparently adding the below code corrects the problem.
searchController?.dimsBackgroundDuringPresentation = false
swift 'dimsBackgroundDuringPresentation' was deprecated in iOS 12.0 Use the obscuresBackgroundDuringPresentation property instead.
searchController?.obscuresBackgroundDuringPresentation = false
searchController.obscureBAckgroundDuringPresentation = false is deprecated in IOS 12.0, so for me it was issue with other gesture detector added to the tableview , so make sure you dont have any other gesture detector and touchesview method that distort the normal working flow of tablview's delegate method( didSelectAtRow ), hope it will work,
I have a simple tableView, DailyAttendanceController, with a searchbar. Searchbar was added in its viewDidLoad. View TableView here.
import UIKit
class OldCellsTableViewController: UITableViewController {
let data = ["A", "B", "C"]
var searchController:UISearchController!
let resultsController = SearchResultsUpdater()
override func viewDidLoad() {
super.viewDidLoad()
resultsController.data = data
searchController = UISearchController(searchResultsController: resultsController)
let searchBar = self.searchController.searchBar
searchBar.scopeButtonTitles = ["All", "Short", "Long"]
searchBar.placeholder = "Search for a student"
searchBar.sizeToFit()
self.tableView.tableHeaderView = searchBar
self.searchController.searchResultsUpdater = resultsController
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let info = data[indexPath.row]
cell.textLabel?.text = info
return cell
}
}
I then created another class, SearchResultsController, to handle the search. Searching works fine, but space is allocated for DailyAttendanceController's header. View SearchResultsController here
How do I remove this?
Code for my SearchResultsController:
import UIKit
import CoreData
class SearchResultsController: UITableViewController, UISearchResultsUpdating {
var data:[String] = []
var filteredData:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
filteredData.removeAll(keepCapacity: true)
let searchString = searchController.searchBar.text
for info in data {
if (info.uppercaseString.rangeOfString(searchString.uppercaseString) != nil) {
filteredData.append(info)
}
}
tableView.reloadData()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = filteredData[indexPath.row]
return cell
}
}