How do you create a tableView inside a tableViewCell? - ios

I have been searching for some time now and have developed a massive headache trying to solve this problem. Basically, I would like to have a parent tableView mainTableView to contain a tableViewCell that includes a label and another tableView innerTableView. This problem is not as simple as I had hoped.
If there is a better way to implement what I want with different components, please let me know.
Otherwise, I have tried to simplify the code to its bare bones with simple data. Please assume I have correctly connected all my outlets properly. I have 3 swift files for this simplified version. ViewController.swift is the main VC that holds the mainTableView. MainTableViewCell.swift is the main cell that will hold a label and a tableView innerTableView in it. InnerTableViewCell is the final cell that will be associated with the innerTableView. In this cell, there is only a label. I have been successful in creating this first layer and the mainNameLabel.text shows up properly on the main storyboard. However, I have configured MainTableViewCell to the best of my knowledge and have come short to success. The innerTableView does not appear on the main storyboard.
Below, I will provide the code of each swift file:
ViewController.swift
//
// ViewController.swift
// NestedTableViews
//
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var mainTableView: UITableView!
//fake data
var arr = ["Hello", "Hi", "Whats up", "How's it going"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
mainTableView.delegate = self
mainTableView.dataSource = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainTableViewCell") as! MainTableViewCell
let word = arr[indexPath.row]
cell.mainNameLabel.text = word
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
}
MainTableViewCell.swift
//
// MainTableViewCell.swift
// NestedTableViews
//
import UIKit
class MainTableViewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var mainNameLabel: UILabel!
#IBOutlet weak var innerTableView: UITableView!
var innerArr = ["How are you?", "How are you doing?", "What's the matter?"]
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
innerTableView.dataSource = self
innerTableView.delegate = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InnerTableViewCell") as! InnerTableViewCell
let innerWord = innerArr[indexPath.row]
cell.innerNameLabel.text = innerWord
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return innerArr.count
}
}
InnerTableViewCell
//
// InnerTableViewCell.swift
// NestedTableViews
//
import UIKit
class InnerTableViewCell: UITableViewCell {
#IBOutlet weak var innerNameLabel: UILabel!
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
}
}
Here is what the storyboard looks like in this current state:
What is it that I am doing wrong? I could ask 100 questions but regardless, I am still very stuck here. How do I get the nested tableView innerTableView to appear and with the proper data?
Thank you in advance

Related

iOS - UITableView inside a UIView

I want to display a UITableView inside a UIViewController. This View Controller contains a UISegmentedControl with two screens (FirstViewControllerand SecondViewController).
The first View Controller is the one that contains the UIViewTable (please don't mind the second).
When I execute the app in the simulator, everything works fine, but when I try to scroll the table view in the first ViewController, the cells disappear. The only way to make them reappear is to kill the app and reopen it again.
I'm new to iOS development (I come from Android), and I'm obviously missing something here.
I already tried adding a UIViewTable outside a container UIView and it works fine. So I'm guessing the problem has to do with the container or the segmented control...
Here's my implementation:
Storyboard
UIViewController with UISegmentedControl and UIView (which will contain the two screens of the segmented control).
View Controller
#IBOutlet weak var container: UIView!
var sectionViews:[UIView]!
override func viewDidLoad() {
super.viewDidLoad()
sectionViews = [UIView]()
sectionViews.append(FirstViewController().view)
sectionViews.append(SecondViewController().view)
for v in sectionViews {
container.addSubview(v)
}
container.bringSubviewToFront(sectionViews[0])
}
#IBAction func switchViewsAction(_ sender: UISegmentedControl) {
self.container.bringSubviewToFront(self.sectionViews[sender.selectedSegmentIndex])
}
First View Controller
The FirstViewController has a swift and a xib files, and has two files Cell.swift and Cell.xib for the table cell.
FirstViewController.swift
#IBOutlet weak var tableView: UITableView!
let cellID = "CellId"
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "Cell", bundle: nil), forCellReuseIdentifier: cellID)
self.tableView.dataSource = self
self.tableView.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! Cell
cell.label.text = "\(indexPath.row)"
return cell
}
FirstViewController.xib
Cell.xib
Any help will be appreciated!
Thanks
One obvious problem is that you are saying container.addSubview(v) without giving v any frame or constraints. Since you use autolayout to position container, you ought to use autolayout to position v as well. You should set its top, bottom, leading, and trailing anchors to equal those of container with a constant of zero. (And set its translates... to false.) Do that for both cases of v in the loop.
However, there is much more serious problem, which is that the view controllers that you create by saying FirstViewController() and SecondViewController() are not retained. Therefore they vanish in a puff of smoke. They thus lose their functionality; for example, the table view no longer has any data source or delegate so it has no cells.
What you are doing is totally illegal. You cannot simply use a view controller to "dumpster-dive" as a way of grabbing its view and shove its view, willy-nilly, into the interface. You must make the view controller a child view controller of your parent view controller (Item in this case). There is an elaborate dance you must do in order to ensure that the child view controller has its proper place in the view controller hierarchy and receives in good order all the messages that a view controller must receive, and you are not doing the dance.
For examples of how to do the dance, see for instance my answers
https://stackoverflow.com/a/41898819/341994
and
https://stackoverflow.com/a/52666843/341994
import UIKit
class TestViewController: UIViewController , UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var segmentControlOutlet: UISegmentedControl!
#IBOutlet weak var tableView: UITableView!
var arrayName = ["Label1", "Label2", "Label3","Label4","Label5","Label6","Label7","Label8","Label9","Label10"]
var arrayName2 = ["Label1", "Label2", "Label3","Label4","Label5"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if segmentControlOutlet.selectedSegmentIndex == 0 {
return arrayName.count
}else{
return arrayName2.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell
if segmentControlOutlet.selectedSegmentIndex == 0 {
cell.textLabel?.text = arrayName[indexPath.row]
}else{
cell.textLabel?.text = arrayName2[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
}
And this code is for UITableViewCell Class:-
import UIKit
class TestTableViewCell: UITableViewCell {
#IBOutlet weak var labelName: UILabel!
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
}
}

Label not appearing in Swift table

No data is appearing in my Swift table. I'm fairly new to Swift and not quite sure why this or what I might be missing. I followed the guide here for the most part with some differences:
Apple Table Creation
Here's the tableView definition:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "AccountTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? AccountTableViewCell else {
fatalError("The dequeued cell is not an instance of AccountTableViewCell.")
}
let item = userDataSource[indexPath.row]
// Dummy values just to test this out
cell.leftLabel.text = "test1";
cell.rightLabel.text = "test2";
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1;
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->Int {
return userDataSource.count;
// This should be an array value, but I have also tried passing a static int here as well to test
}
Here is my class definition with the implemented procotols:
class AccountViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
And here is my table cell definition:
class AccountTableViewCell: UITableViewCell {
//MARK: Properties
#IBOutlet weak var leftLabel: UILabel!
#IBOutlet weak var rightLabel: UILabel!
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
}
}
I've got both rightLabel and leftLabel setup in the Storyboard.
I can go to the account page represented by this view controller and a table display does come up - it just has absolutely no data in it.
What am I missing?
It is not sufficient to simply add a UITableView to your view controller scene. You must set the tableview's dataSource property to your view controller instance in the Storyboard connections inspector for the tableview.

Swift 3 UISwitch in TableViewCell loses State when scrolling

That's weird: I just set up a new Single-View iOS Project and put a TableView into the Main.storyboard. In this TableView I put a TableViewCell and into this Cell, I put an UILabel and an UISwitch.
For this TableViewCell I created a CocoaTouchClass MyTableViewCell and set this for the TableViewCell's Class in Interfacebuilder.
I connected Outlets for the UILabel and UISwitch to MyTableViewCell, as well as an action for the switch.
Also I connected the TableView's dataSource and delegate to the ViewController.
So, as I think, basic stuff for setting up a table.
My ViewController looks like this:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableData = [[String: Bool]]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for index in 1...40 {
self.tableData.append([String(index): index%2 == 0])
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "mycell", for: indexPath) as! MyTableViewCell
let object = tableData[indexPath.row].first!
cell.myLabel.text = object.key
cell.mySwitch.setOn(object.value, animated: false)
return cell
}
}
So, I populate the Table with some Rows of Data and switch every second UISwitch to on.
The MyTableViewCell-Class is nothing special as well:
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var mySwitch: UISwitch!
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
}
#IBAction func switched(_ sender: UISwitch) {
print("Switched: \(sender.isOn)")
}
}
Ok, fire up the iOS-Simulator and I see the Table as expected. 40 Table-Lines don't fit on one screen, so the TableView makes itself scrollable.
Now: when I change the state of one UISwitch, and drag the TableView so the changed UISwitch gets out of view and then drag the TableView so the changed UISwitch gets visible again, it is changed back to its initial state.
The Switch-event gets fired like it should.
So, what am I doing wrong? Am I missing something?
I recorded a 4-seconds-Screencast to demonstrate, what's going on:
http://b-bereich.de/download/swiftSwitch.mov
TableViewCells are reused.
That means you need to keep track of the data you are using to fill the content of the cells. If the cell content changes - such as when you tap a Switch in the cell - you need to update your datasource. When you scroll, and that row is displayed again, your data will know how to set the state of the Switch.
Here is a simple example:
//
// TableWithSwitchTableViewController.swift
// SWTemp2
//
// Created by Don Mag on 6/5/17.
// Copyright © 2017 DonMag. All rights reserved.
//
import UIKit
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var mySwitch: UISwitch!
var switchTapAction : ((Bool)->Void)?
#IBAction func switched(_ sender: UISwitch) {
print("Switched: \(sender.isOn)")
// send the Switch state in a "call back" to the view controller
switchTapAction?(sender.isOn)
}
}
// simple data object
class MyObject: NSObject {
var theTitle = ""
var theSwitchState = false
init(_ title: String) {
theTitle = title
}
}
class TableWithSwitchTableViewController: UITableViewController {
// array of MyObjects
var myData = [MyObject]()
override func viewDidLoad() {
super.viewDidLoad()
// just to make it a little easier to see the rows scroll
tableView.rowHeight = 60
// create 40 data objects for the table
for i in 1...40 {
let d = MyObject("Data Item: \(i)")
myData.append(d)
}
tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SwitchCell", for: indexPath) as! MyTableViewCell
// Configure the cell...
let d = myData[indexPath.row]
cell.myLabel.text = d.theTitle
cell.mySwitch.isOn = d.theSwitchState
// set a "Callback Closure" in the cell
cell.switchTapAction = {
(isOn) in
// update our Data Array to the new state of the switch in the cell
self.myData[indexPath.row].theSwitchState = isOn
}
return cell
}
}
I think you need to have your IBAction change the Bool each time you flip the switch. Currently the switch changes the UI but not the underlying Bool so the cellForRowAt method uses the currently saved value when the cell scrolls back on screen.
Add function prepareForReuse() in your custom cell class and set switch state to off by default. -:
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var mySwitch: UISwitch!
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
}
#IBAction func switched(_ sender: UISwitch) {
print("Switched: \(sender.isOn)")
}
override func prepareForReuse(){
// SET SWITCH STATE OFF HERE
}

How can I display multiple string values to multiple labels in a custom TableView Cell in swift ios?

var leadername = ["1","2","3","4"]
var districts = ["Delhi","Kerala"]
override func viewDidLoad() {
leadTableSetup()
super.viewDidLoad()
}
func leadTableSetup(){
LeadTableView.delegate = self
LeadTableView.dataSource = self
self.LeadTableView.register(UINib(nibName: "LeaderBoardTableViewCell", bundle: nil), forCellReuseIdentifier: "leadCell")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 14
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "leadCell") as! LeaderBoardTableViewCell
// Set text from the data model
cell.areaLbl.text = districts[indexPath.row]
cell.leaderNameLbl.text = leadername[indexPath.row]
return cell
}
I have declared two strings and I need to display these strings in the labels in my custom collection view cell that I have created. How can I achieve this? I need to display "leadername" string in one label and "districts" label in another label.
Go with this demo, Shared Demo
After the demo, If you still face any problem then let me know.
Now Listen Here
I think you need output something like this,
Follow the steps: -
Create a new viewcontroller(says, CustomTableVC) in your storyboard and one UITableView(give constraints and delegate to its own class), take outlet of UItableView (says, tblMyCustom)
Now press CLT+N for new file and do like this below image, Subclass - UItableViewCell and also tick on XIB option.
Open our xib file, add new UIView (says myView, as you see highted in below image), in this myView add two labels
Now take outlet of these two labels in its customCell class
class CustomTableCell: UITableViewCell {
#IBOutlet var lblLeaderNo: UILabel!
#IBOutlet var lblDistict: UILabel!
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
}
}
Now back to your Viewcontroller Class
import UIKit
class CustomTableVC: UIViewController , UITableViewDelegate, UITableViewDataSource{
#IBOutlet var tblMyCustom: UITableView!
var leaderno : [String]!
var distict : [String]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tblMyCustom.register(UINib(nibName: "CustomTableCell", bundle: nil), forCellReuseIdentifier: "customCell")
self.leaderno = ["1", "2", "3", "4"]
self.distict = ["Delhi","Kerala", "Haryana", "Punjab"]
// above both array must have same count otherwise, label text in null
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return leaderno.count;
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
var customCell: CustomTableCell! = tableView.dequeueReusableCell(withIdentifier: "customCell") as? CustomTableCell
customCell.lblLeaderNo.text = self.leaderno[indexPath.row]
customCell.lblDistict.text = self.distict[indexPath.row]
return customCell
}
}
above all is code of VC, it is not getting settle down here in single code format, I dont know why.
Now, follow these steps you get output as i show you image in starting of the procedure.

Troubleshooting Custom UITableView Cell [Swift]

I have created a custom UITableView cell in my Storyboard that looks like this:
I hooked it up to my UITableViewCell class like this:
import UIKit
class StatusCell: UITableViewCell {
#IBOutlet weak var InstrumentImage: UIImageView!
#IBOutlet weak var InstrumentType: UILabel!
#IBOutlet weak var InstrumentValue: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Finally I attempted to initialize the UITableView from my UIViewController as such:
import UIKit
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var TableView: UITableView!
let Items = ["Altitude","Distance","Groundspeed"]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.Items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: StatusCell! = tableView.dequeueReusableCellWithIdentifier("Cell") as StatusCell!
cell.InstrumentType?.text = Items[indexPath.row]
cell.InstrumentValue?.text = "150 Km"
cell.InstrumentImage?.image = UIImage(named: Items[indexPath.row])
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
However, when I attempt to run the program I get an EXC_BAD_INSTRUCTION error:
What could I have done wrong? Any help would be appreciated!
The debugger output shows that cell is nil, meaning it could not be instantiated. Furthermore, you are forcing an unwrapping of the optional (using the !) which causes the app to crash on the nil value.
Try to change your cellForRowAtIndexPath method like so (notice the dequeueReusableCellWithIdentifier method):
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as StatusCell
cell.InstrumentType.text = Items[indexPath.row]
cell.InstrumentValue.text = "150 Km"
cell.InstrumentImage.image = UIImage(named: Items[indexPath.row])
return cell
}
Assuming that your custom tableViewCell class is correctly set up and the outlets are bound, there is no need to check for optionals.
Place a breakpoint on the let cell = ... line and step through the code. Check if cell gets initialized and is not nil.
And please: Do not use upper case names for properties and variables (your outlets, Items array...) as upper case names are for classes, structs, ...

Resources