Index out of range when selecting reusable tableview cell - swift

I have the following class to keep track of all of the cells selected in the tableview, the issue is I keep receiving the error Index out of Range on the line:
self!.myModels[indexPath.row].isSelected ? self?.selectRow(indexPath) : self?.unselectRow(indexPath)
What is causing this? The issue occurs as soon as the tableView loads.
Rest of the code:
class CheckableTableViewCell: UITableViewCell {
var handler: ((Bool)->())?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.accessoryType = selected ? .checkmark : .none
handler?(selected)
}
}
class TableViewController: UITableViewController {
private var myModels: [MyModel] = [MyModel(isSelected: false),MyModel(isSelected: true),MyModel(isSelected: true),MyModel(isSelected: false) ]
var sections = [Section]()
var structure = [Portfolios]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckableTableViewCell
cell.handler = {[weak self] selected in
self?.myModels[indexPath.row].isSelected = selected
}
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
cell.textLabel?.textAlignment = .left
cell.selectionStyle = .none
let titleStr = "\(item.code)
cell.textLabel!.text = titleStr
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndexPath = indexPath.row
myModels[selectedIndexPath].isSelected = true
self.tableView.reloadRows(at: [indexPath], with: .none)
}
Sections defines the following tableView struct:
struct Section {
let name : String
var items : [Portfolios]
}
struct Portfolios: Decodable {
let code: String
enum CodingKeys : String, CodingKey {
case code
}
}

Related

Programmatic swift: TableView inside a TableView

I'm currently trying to put a tableview inside a tableview, but programmatically (in order to understand better wtf I'm doing in the storyboard way of doing this). I'm currently stuck at showing the inner table, any help please?
class ContactsViewController: UIViewController, UITabBarDelegate,
UITableViewDataSource, UITableViewDelegate {
private let contacts = ContactAPI.getData()
let contactsTableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(contactsTableView)
contactsTableView.translatesAutoresizingMaskIntoConstraints = false // enable Auto-Layout
contactsTableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
contactsTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contactsTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
contactsTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
contactsTableView.dataSource = self
contactsTableView.delegate = self
contactsTableView.register(ContactsCell.self, forCellReuseIdentifier: "contactCell")
contactsTableView.rowHeight = 1000
contactsTableView.estimatedRowHeight = 100
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactsCell
cell.jobLabel.text = contacts[indexPath.row].jobTitle
cell.nameLabel.text = contacts[indexPath.row].name
return cell
}
}
The external TableViewCell is given by
class ContactsCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(image)
contentView.addSubview(nameLabel)
contentView.addSubview(jobLabel)
contentView.addSubview(tablello)
}
// working code that defines image, nameLabel, jobLabel
let tablello: UITableView = {
let tab = UITableView()
let data = innerTable()
tab.dataSource = data
tab.delegate = data
tab.frame = CGRect (x: 0, y: 0, width: 300, height: 300)
tab.register(gInnerCell.self, forCellReuseIdentifier: "gennaroCell")
tab.translatesAutoresizingMaskIntoConstraints = false
return tab
}()
}
And relative classes
class innerTable: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gennaroCell", for: indexPath) as! gInnerCell
cell.labelloNapoli.text = "\(indexPath.row)"
print("AO GENNĂ ")
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("loaded inner")
}
}
class gInnerCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(labelloNapoli)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
let labelloNapoli:UILabel = {
let lable = UILabel()
lable.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
return lable
}()
}
I've tried turning tablello into a separate View, and adding there the table as a subview, but (although it printed the inside print() ) it messes up all my UI.
In general what are you trying to accomplish by putting a tableView inside of another tableView? Are you able to display the same information using a custom tableViewCell? Or better yet use a CollectionView as they are very flexible when it comes to UI. Sorry for not answering the question; Just trying to get a better understanding of what you are trying to accomplish UI wise.

Getting id by clicking on tableView

By clicking on the red area I get a comment id. But I also want to get the id if I click on the blue button. How can I do that?
Right now I use this to detect a tap on a row. But tapping on button should run some other code.
extension FirstTabSecondViewComment: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
FirstTabSecondViewComment.subComment = table[indexPath.row].commentId ?? ""
print(FirstTabSecondViewComment.subComment)
self.performSegue(withIdentifier: "CommentDetail", sender: Any?.self)
}
}
After you have register your custom cell declare it in cellForRowAt and add target in button cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! MyCell // your custom cell
// add target in buoon cell
cell.YourButtonCell.addTarget(self, action: #selector(submit(_:)), for: .touchUpInside)
return cell
}
after that add submit func:
#objc fileprivate func submit(_ sender: UIButton) {
var superview = sender.superview
while let view = superview, !(view is UITableViewCell) {
superview = view.superview
}
guard let cell = superview as? UITableViewCell else {
print("button is not contained in a table view cell")
return
}
guard let indexPath = tableView.indexPath(for: cell) else {
print("failed to get index path for cell containing button")
return
}
// We've got the index path for the cell that contains the button, now do something with it.
print("button is in index \(indexPath.row)")
}
This is a full code example, copy and paste in a new project and run:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let tableView = UITableView()
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.register(MyCell.self, forCellReuseIdentifier: cellId)
}
#objc fileprivate func submit(_ sender: UIButton) {
var superview = sender.superview
while let view = superview, !(view is UITableViewCell) {
superview = view.superview
}
guard let cell = superview as? UITableViewCell else {
print("button is not contained in a table view cell")
return
}
guard let indexPath = tableView.indexPath(for: cell) else {
print("failed to get index path for cell containing button")
return
}
// We've got the index path for the cell that contains the button, now do something with it.
print("button is in index \(indexPath.row)")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCell
// add target in buoon cell
cell.cancelButton.addTarget(self, action: #selector(submit(_:)), for: .touchUpInside)
return cell
}
}
class MyCell: UITableViewCell {
let cancelButton: UIButton = {
let b = UIButton(type: .system)
b.backgroundColor = .white
b.setTitle("get Index", for: .normal)
b.layer.cornerRadius = 10
b.clipsToBounds = true
b.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
b.tintColor = .black
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .darkGray
contentView.addSubview(cancelButton)
cancelButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
cancelButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
cancelButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
cancelButton.widthAnchor.constraint(equalToConstant: 300).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

How to remove an item in an Array if deselecting it in tableView swift

that if I deselect an item in tableView it didn't get deletet from the Array. When I select an item it works fine but if I want to deselect I don't know how to code it. My Question is how I can delete this item from the Array.
Here is the tableView code:
var highlightsArray: [String] = []
var highlightRow: String = ""
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return highlightsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "com.nils.PrototypeCellHighlights", for: indexPath) as? CheckableTableViewCell
let highlights = highlightsArray[indexPath.row].components(separatedBy: ", " )
cell?.textLabel?.text = highlights.first
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView == highlightTableView {
highlightRow = highlightsArray[indexPath.row]
let selected_indexPaths = tableView.indexPathsForSelectedRows
for indexPath in selected_indexPaths! {
let highlightName = tableView.cellForRow(at: indexPath)
let highlightText = (highlightName?.textLabel?.text)!
if highlightValue.contains(highlightText) {
}
else {
highlightValue.append((highlightName?.textLabel?.text)!)
}
}
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
}
Additionally I have a class for the tableView Cell:
class CheckableTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.accessoryType = selected ? .checkmark : .none
}
}
At least here is the finish Button:
#objc func doneButton() {
if highlightValue == [] {
let alert = UIAlertController(title: "Error", message: "You didn't select anything", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
print(highlightValue)
highlightValue = []
}
If you have any questions please let me know
Do you want to remove an item from the array when you deselect the row?
Try,
didDeselectRowAt{
highlightValue.remove(at:indexpath.row)
table.reloadData()
}

How do I inject or add data to my table as it isn't working?

I just cannot seem to update data in Swift! I am trying to build a radio player app for a friends radio station so when a song changes I need to update the playlist viewcontroller.
The data from my Main View Controler is a instance of a struct. I know there is data being generated and it is passed to the table but for whatever reason the array isn't updating. I am sure it is something simple.
I have tried directly injecting the data with a call, using a protocol and using a function. Using the protocol and function I can see the passed data via print statement.
class PlaylistVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
//Mark: Variables ~~~~~~~~~~~~###########################################
var sourceDatas = [PlaylistData]()
//Mark: View Containers ~~~~~############################################
private let bg = GradientBG()
private let closeButton = UIButton()
let playlistTable = UITableView()
//Mark: Overrides ~~~~~~~~~~~~###########################################
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
playlistTable.delegate = self
playlistTable.dataSource = self
layoutUI()
setupTable()
setupClose()
}
//Mark: objc func's ~~~~~~~~~~~###########################################
#IBAction func handleXButton() {
dismiss(animated: true, completion: nil)
}
func handleMoreInfo(_ playlist: PlaylistData) {
let vc = SongPopUp()
vc.buildLables(playlist)
vc.modalPresentationStyle = .overCurrentContext
self.present(vc, animated: true, completion: nil )
}
//##################################################################
//Mark: Pass Data ##################################################
//Mark: Not Working!! ##############################################
//##################################################################
func handlePlaylist(_ with: PlaylistData) {
print(with)
sourceDatas.append(with)
//sourceDatas.insert(with, at: 0)
playlistTable.reloadData()
}
//Mark: Table view ################################################
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sourceDatas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = UITableViewCell(style: .subtitle, reuseIdentifier: "myCell")
cell.backgroundColor = .clear
cell.selectionStyle = .none
tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let text = cell.textLabel {
components.layoutHeadingLable(sender: text, title: sourceDatas[indexPath.row].heading, size: 20)
}
if let dtext = cell.detailTextLabel {
components.layoutSubheadingLable(sender: dtext, title: sourceDatas[indexPath.row].subheading, size: 14)
}
if sourceDatas[indexPath.item].hasStoreLink {
cell.accessoryType = .detailButton
cell.tintColor = .white
}
return cell
}
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let dataToPass = self.sourceDatas[indexPath.row]
print("Extended~~~~~~~~~~~~~~~~~~~~")
print(dataToPass)
handleMoreInfo(sourceDatas[indexPath.row])
}
//Mark: Setup Table ~~~~~~~~~~~~~~###########################################
func setupTable() {
playlistTable.backgroundColor = .clear
playlistTable.separatorStyle = .singleLine
playlistTable.rowHeight = 45
playlistTable.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
}
......
I think it's something wrong with your cellForRowAt
Try to make it simple first, like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! UITableViewCell
cell.textLabel?.text = sourceDatas[indexPath.row]
return cell
}
See whether you can find the added object. Then dive into the detail settings of your cell.

How to add different accessories(or subviews) for each cell in swift?

I made 4 identical cells with subviews by using UITableViewCell subclass 'FruitTableViewCell' class.
FruitTableViewCell.swift
class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {
var fruitsTextField = UITextField()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextField)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextField.frame = CGRect(x: 100, y: 7.5, width: 50, height: 30)
fruitsTextField.backgroundColor = UIColor.darkGray
fruitsTextField.delegate = self
}
}
TableViewController.swift
class TableViewController: UITableViewController, UITextFieldDelegate {
let fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]
let cellReuseidentifier = "cell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruitsComponents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
}
It works well.
But in fact, I want to add different accessories(or subviews) for each cell. Row 0 for UITextField, Row 1 for UILabel, Row 2 for Stepper, Row 3 for UILabel, ... and so on.
So I made the other UITableViewCell subclass 'AnotherFruitTableViewCell' class to use.
And I tried by using 'if' statement.
revised TableViewController.swift
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
But the message 'could not cast value of type' poped up.
Because of this code, I think.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
}
And fundamentally, I think 'if' statement is not a good way to add different accessories for each cells.
How can I add different accessories(or subviews) for each cell?
You registered your FruitTableViewCell but not registered AnotherFruitTableViewCell