I have table view in which cell consist of multiple sections, each section have different number of rows and every row have a text field. When I wrote in it and scroll down and up the data lost or reshuffled. So I am trying to save textfield data into the 2 dimensional array but I can’t solve this problem.
Here is code in Custom cell:
class CustomCell: UITableViewCell {
#IBOutlet weak var userName: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
View controller code:
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel()
label.text = "Header"
return label
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableview.dequeueReusableCell(withIdentifier: CustomCell.identifier, for: indexPath) as! CustomCell
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80.0
}
}
Your cells cannot be expected to maintain data (the contents of your UITextField) as they get removed from memory once they are outside the visible bounds of your table view.
You have to look into the UITableViewDataSource protocol and store the contents of your cell’s UITextFields in a class which will remain in memory for the duration of your table view’s view controller.
Typically, people use the view controller to be the Data Source as you have done.
Steps are as follows:
In your view controller's initialization, create and prepare a data structure (array / dictionary keyed on IndexPaths) that will contain the contents of the text you need to store
When dequeuing a cell (in your cellForRowAt function), configure the cell with the necessary string from your data structure, if content exists for that particular indexPath.
When the text is changed by the user in the cell, notify your data source of new contents for the cell's index path
Example:
Define the following protocol:
protocol DataSourceUpdateDelegate: class {
func didUpdateDataIn(_ sender: UITableViewCell, with text: String?)
}
Ensure your UITableViewCell declares a delegate variable and uses it:
class MyCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet var myTextField: UITextField!
weak var dataSourceDelegate: DataSourceUpdateDelegate?
func configureCellWithData(_ data: String?, delegate: DataSourceUpdateDelegate?)
{
myTextField.text = data
myTextField.delegate = self
dataSourceDelegate = delegate
}
override func prepareForReuse() {
myTextField.text = ""
super.prepareForReuse()
}
func textFieldDidEndEditing(_ textField: UITextField) {
dataSourceDelegate?.didUpdateDataIn(self, with: textField.text)
}
}
Make sure your View Controller conforms to DataSourceUpdateDelegate and initialize a variable to manage the data:
class MyViewController: UITableViewController, UITableViewDataSource, DataSourceUpdateDelegate {
var data = [IndexPath : String]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCell() // Dequeue your cell here instead of instantiating it like this
let cellData = data[indexPath]
cell.configureCellWithData(cellData, delegate: self)
return cell
}
func didUpdateDataIn(_ sender: UITableViewCell, with text: String?) {
data[tableView.indexPath(for: sender)!] = text
}
}
Related
I have a graph view in a cell of a TableViewController and I would like to pass an array from the table view controller to that view. How should I tackle the problem?
You have to bypass data to cell first then to your graph view. Try this:
class GraphView: UIView {
var points: [CGPoint]
}
class GraphCell: UITableViewCell {
#IBOutlet weak var graphView: GraphView!
func fillData(_ points: [CGPoint]) {
graphView.point = points
}
}
And in your controller:
class GraphTableViewController: UIViewController, UITableViewDataSource {
var points: [CGPoint] = [
CGPoint.zero,
CGPoint(x: 100, y: 100)
]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "GraphCell") as? GraphCell else {
return UITableViewCell()
}
cell.fillData(points)
return cell
}
}
The main way this is accomplished is via the use of the func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { } in which you can set up your individual cell, usually via typecasting after dequeing the cell for index path.
If you're new to iOS development welcome! And I'm sure there was more than one confusing word in post above, but you'll get used to these terms soon!
Check out iOS's example for an example of how this method actually works.
Overview
I'm trying to better understand how extensions work.
In my app I have a ViewController. There I put a view of another class. In this custom class I put a bunch of buttons and a table view. I want them to display some text inside of my tableView whenever I press them.
The problem is that I want to edit some of the table view functions in order to better adjust it to my ViewController.
What I know
All I know is based on the apple documentation
What I'm doing
What I'm trying to do, I should say, is to add functionality to a custom view's function after adding an object which is of the type of my custom class to the ViewController.
This is my custom class:
class CustomClass: UIView{
#IBOutlet weak var abtn: UIButton!
#IBOutlet weak var table: UITableView!
func setupTable(){
table.delegate = self
table.dataSource = self
table.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
table.backgroundColor = UIColor.black.withAlphaComponent(0.1)
}
}
extension CustomClass: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = table.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("I want to add stuff here too")
}
//And more stuff that is not useful rn
}
Inside of the ViewController class I have declared a variable of type CustomClass.
#IBOutlet weak var custom: CustomClass!
In my viewDidLoad I call :
custom.setupTable()
What I need to do is creating an extension to edit the tableview that belongs to custom (the variable of type CustomClass that is inside of my ViewController).
I have no clue on how to do that.
I know how to work with extension to expand my code's functionality but I don't know how to use them to edit these other functions.
Question
How do I edit the tableview functions that belong to custom?
Ie. how would I be able to change the number of rows or to change the cell's layout from the class I call the object in?
I hope I was clear enough...
For this specific example...
Add a property to your CustomClass:
class CustomClass: UIView {
// this may be changed by the "calling class"
var numRows: Int = 10
#IBOutlet weak var abtn: UIButton!
#IBOutlet weak var table: UITableView!
func setupTable(){
table.delegate = self
table.dataSource = self
table.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
table.backgroundColor = UIColor.black.withAlphaComponent(0.1)
}
}
In your extension, use that property:
extension CustomClass: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// don't make this a hard-coded number
//return 10
return numRows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = table.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
return cell
}
//And more stuff that is not useful rn
}
Then, in your "calling class", you can change that property:
class ExampleViewController: UIViewController {
let myView = CustomClass()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myView)
// constraints, etc
// change the number of rows in the table in myView
myView.numRows = 20
}
}
More likely, though, you would be doing something like setting / changing the data for the table in your custom class.
Here's an example, along with showing how to use a closure to "call back" to the calling class / controller:
class CustomClass: UIView {
// this may be changed by the "calling class"
var theData: [String] = []
// closure to "call back" to the controller
var callback: ((IndexPath) -> ())?
#IBOutlet weak var abtn: UIButton!
#IBOutlet weak var table: UITableView!
func setupTable(){
table.delegate = self
table.dataSource = self
table.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
table.backgroundColor = UIColor.black.withAlphaComponent(0.1)
}
}
extension CustomClass: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return theData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = table.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
cell.textLabel?.text = theData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// tell the controller the cell was selected
callback?(indexPath)
}
}
class ExampleViewController: UIViewController {
let myView = CustomClass()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myView)
// constraints, etc
// set the data in CustomClass
myView.theData = [
"First row",
"Second row",
"Third",
"Fourth",
"etc..."
]
myView.callback = { indexPath in
print("CustomClass myView told me \(indexPath) was selected!")
// do what you want
}
}
}
A bit of context:
I'm filling a tableView with movie titles
my table
When one of those rows gets selected I want to go to the movie detail
but when I tap any of the rows nothing happens.
class SearchTableViewController: NavigationController{
#IBOutlet weak var tableView: UITableView!
var filteredMovies = [Movie]()
let request = Requests()
override func viewDidLoad() {
super.viewDidLoad()
}
/*
searches movies from the db which title corresponds to the given text
*/
func searchMovie(_ keywords: String) {
request.searchMovie(keywords: keywords){ response in
for data in response{
self.filteredMovies.append(data)
}
DispatchQueue.main.sync {
self.tableView?.reloadData()
}
}
}
}
extension SearchTableViewController : UITableViewDelegate { }
extension SearchTableViewController: UISearchBarDelegate {
/*
every time a key gets pressed, the table view gets updateted
with new movie titles
*/
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.filteredMovies = [Movie]()
searchText.isEmpty ? self.tableView?.reloadData() : searchMovie(searchText)
}
}
extension SearchTableViewController : UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredMovies.count
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
print("hello from highlight")
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("hello from selection")
}
func tableView(_ tableView: UITableView, didFocusRowAt indexPath: IndexPath) {
print("hello from focus")
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SearchTableCell
cell.onDidSelectMovie = {
super.goToMovieDetail(movie: self.filteredMovies[indexPath.row])
}
let label = cell.contentView.subviews.first as! UILabel
label.text = filteredMovies[indexPath.row].title
return cell
}
}
As you can see i tested every function that may show that a row got tapped but none of them works.
I also tried to give to the cells a custom class where I override the behavior of the cell property "isSelected".
class SearchTableCell: UITableViewCell {
var onDidSelectMovie:(()->Void)?
override var isSelected: Bool{
didSet {
if isSelected{
if let cb = onDidSelectMovie{
cb()
}
}
}
}
}
Another thing that I have want to point out, which may help, is that "selection" on a cell is enabled like "user interactions" but if I try to change selection from "default" to "blue" the color doesn't change.
I literally ran out of ideas and tried many possible solutions but none of them worked. Any suggestion?
Edit:
I'm going to add everything that can be useful
the tableView delegate is SearchTableViewController
It looks like I had just to remove and add again the tableView delegate... don't ask me why, I've spent 2 days on this. Thanks anyway to everyone who tried to help.
I have a UITextField within a Custom UITableViewCell.
I need to segue to a new view controller when the UITextField that sits within the custom cell, is tapped.
Attempted Solutions
I tried performing the segue inside the CustomTableViewCell class using
public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
print("Text Field Tapped")
return false
}
however this didn't work becuase performSegue is a ViewController function. Then I tried
func someAction() {
performSegue(withIdentifier: "identifier", sender: self)
}
in MyViewController class, however that also did not work (not sure why). This is what my two classes look like:
MyViewController (holds UITableView)
import UIKit
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:SearchTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! SearchTableViewCell
return cell
}
}
CustomTableViewCell
import UIKit
class CustomTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var someTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
self.someTextField.delegate = self
}
}
Any help that you smart people can provide is greatly appreciated :)
something like this should work:
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TFCell", for: indexPath) as! TFCell
cell.textField.delegate = self
return cell
}
}
extension TableViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
performSegue(withIdentifier: "TFSegue", sender: textField)
return false
}
}
class TFCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
}
"TFSegue" is a segue from "TableViewController" to the targetviewcontroller created in storyboard. feel free to ask if anything is unclear!
I am new to iOS dev and basically I'm trying to populate a TableView with String values from an array.
However when I run the app, blank rows show up and no text values are shown. Have I coded this correctly?
import UIKit
class SelectIssueController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var issuesTableView: UITableView!
var issues = [Issue]()
override func viewDidLoad() {
super.viewDidLoad()
self.issues = ["Test1", "Test2", "Test3"]
self.issuesTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override var preferredStatusBarStyle: UIStatusBarStyle{
return UIStatusBarStyle.lightContent
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.issues.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.issuesTableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
cell.textLabel?.text = issues[indexPath.row]
//Even if I manually set a value, the rows are still blank
//cell.textLabel?.text = "Hello World"
return cell
}
}
You can set Table view data source and delegate in two ways.
1. Click Cntrl+drag from tableView to view controller. See below figure
Create the outlet of your tableView and assign its datasource and delegate in ViewDidLoad.
In your example you already have an outlet to issuesTableView, so you would write:
issuesTableView.dataSource = self
issuesTableView.delegate = self
Thanks:)