Passing data from TableViewController to a view in controller's cell - ios

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.

Related

How to place table cells different way when orientation is horizontal in swift

sorry to bother programmers again,
I'm writing a simple app, and one of the view controllers has a table.
I'm new to all of this so I found some videos and programmed the table. This is the code above classes (honestly I still don't understand what it does):
extension ViewController3: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me!")
}
}
extension ViewController3: UITableViewDataSource
{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
And this is the class:
class ViewController3: UIViewController {
#IBOutlet var tView: UITableView!
let names = [
"Alex",
"Andrew",
"Mary",
"Henry"]
override func viewDidLoad() {
super.viewDidLoad()
tView.delegate = self
tView.dataSource = self
}
}
It works fine but the task is when the orientation of the phone/simulator is horizontal, to SHOW the table not in one row, but 2x2 (like 4 cells - 2 rows, 2 lines). I don't know where to search it, I found some tips about placing elements in this way but it's not for a table. And I don't know whether its possible or not. Thank you all in advance

How To Access UITableView.visibleCells Every Time New Cell Emerge

I have a tableView displaying [Double]. Very Simple. But I also wanna display the average of the onscreen numbers on every row, and the difference between this number and the average.
Because I need to re-calculated the average every time a new row appears, I'm thinking about accessing tableView.visibleCells in cellForRowAt: indexPath method, and then update the average of this row and every other rows on screen, because the average of onscreen rows should be the same for all the onscreen rows.
But then I got this error message [Assert] Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed. Make a symbolic breakpoint at UITableViewAlertForVisibleCellsAccessDuringUpdate to catch this in the debugger and see what caused this to occur. Perhaps you are trying to ask the table view for the visible cells from inside a table view callback about a specific row?
While this is loud and clear, I'm wondering what is the correct way or workaround for this?
Code is very simple
class ViewController: UIViewController {
var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row])"
print(tableView.visibleCells.count) // THIS LINE PRODUCE ERROR
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
}
What I have tried:
I've tried didEndDisplaying and willDisplay, when I added print(tableView.visibleCells.count) to either of them, same error message was given back.
Answer:
You can use UITableView's delegate functions to calculate this.
tableView(_:willDisplay:forRowAt:) is called every time before cell becomes visible, so you can recalculate your average value at this moment. Also, there is tableView(_:didEndDisplaying:forRowAt:) which fires when cell goes off display and also can be used to recalculate.
Documentation:
tableView(_:willDisplay:forRowAt:)
tableView(_:didEndDisplaying:forRowAt:)
UPD:
For calculation use tableView.indexPathsForVisibleItems
Example:
import UIKit
class ViewController: UIViewController {
var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.reloadData()
}
private func calculate() {
let count = tableView.indexPathsForVisibleRows?.count
let sum = tableView.indexPathsForVisibleRows?
.map { data[$0.row] }
.reduce(0) { $0 + $1 }
if let count = count, let sum = sum {
print(sum / Double(count))
}
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row])"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
}

Scrolling Tableview data reshuffle in UITextfield in Swift

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

How to create UI using autolayout which will have expand collapse feature in Swift?

I have a UI to design
I need to create this UI using AutoLayout and i am confused shall i use only UITableViewCell to create this UI or i have to create this using `Section. Also how will i work with expand collapse could someone help me in this.Thanks in advance.
you can achieve this in two way First creating 2 prototype cells one for Header view and other for details view and Second one prototype cells one for details view and create your header in viewForHeaderInSection for each section.
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
so it's easy for you to do with first one here is the code.
import UIKit
public struct Section {
var name: String
var collapsed: Bool
public init(name: String, collapsed: Bool = false) {
self.name = name
self.collapsed = collapsed
}
}
class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var sampleData: [Section] = [Section(name: "Header 1"),Section(name: "Header 2"),Section(name: "Header 3")]
After setting up data expand collapse uitableviewcell
//
// MARK: - View Controller DataSource and Delegate
//
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sampleData[section].collapsed ? 2 : 1
}
func numberOfSections(in tableView: UITableView) -> Int {
return sampleData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Header
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "header")!
return cell
}
// Cell
let cell = tableView.dequeueReusableCell(withIdentifier: "detailed")!
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
let collapsed = !sampleData[indexPath.section].collapsed
// Toggle collapse
sampleData[indexPath.section].collapsed = collapsed
self.tableView.reloadSections([indexPath.section], with: .automatic)
}
}

how to set cell row height in table view if it exceed its indexpath.row?

I am trying to make a FAQ view controller using table view, I need little bit fix in the UI. here is the display of my FAQ VC right now
(please ignore the red line)
as we can see, basically there are 2 row height, 80 & 160. if the row is tapped (selected) the row height will expand from 80 (yellow line) to 160 (purple line).
I want to make the row height under the last question is still 80 not 160. I have tried but I can't set the row below the last question. here is my code I use
class FAQVC: UIViewController {
#IBOutlet weak var retryButton: DesignableButton!
#IBOutlet weak var tableView: UITableView!
var FAQs = [FAQ]()
var selectedIndexs: [IndexPath: Bool] = [:]
override func viewDidLoad() {
super.viewDidLoad()
getFAQData()
}
}
extension FAQVC : UITableViewDelegate, UITableViewDataSource {
// MARK: - Table View Delegate and Datasource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if self.cellIsSelected(indexPath: indexPath) {
return 160
} else {
return 80
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let isSelected = !self.cellIsSelected(indexPath: indexPath)
selectedIndexs[indexPath] = isSelected
tableView.beginUpdates()
tableView.endUpdates()
}
func cellIsSelected(indexPath: IndexPath) -> Bool {
if let number = selectedIndexs[indexPath] {
return number
} else {
return false
}
}
}
Please put this code in your viewDidLoad() method of your controller.
YOUR_TABLE_VIEW.tableFooterView = UIView(frame: .zero)
this will remove extra separators.
Quickest way to do this would be to add an extra empty cell at the end with row height 80
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count + 1
}
Also, make sure you make a change to cellForRowAt method to accommodate this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < FAQs.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
return UITableViewCell()
}
EDIT :
I just read that you don't really require separators after the last cell. In that case look here https://spin.atomicobject.com/2017/01/02/remove-extra-separator-lines-uitableview/

Resources