Creating tableView inside tableView - ios

I've created a tableView with prototype cells. Inside each of these prototype cells is another tableView with different prototype cells. I've linked this all together fine, but I'm having trouble modifying the innermost prototype cells. Here is why.
Here is the relevant code:
class ViewController: UIViewController, AVAudioRecorderDelegate, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "outerCell") as! outerCell
//would obviously make some modification to cell here, like cell.title = "test" or something
let cell2 = cell.commentTableView.dequeueReusableCell(withIdentifier: "innerCell") as! innerCell
cell2.commentText.text = "sus"
//NEED TO DIFFERENTIATE HERE ON HOW TO KNOW WHICH CELL TO RETURN
//e.g. NEED TO RETURN either cell1 or cell2, depending on the tableView
}
My code for outerCell looks like this:
import UIKit
class outerCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var commentTableView: UITableView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
commentTableView.delegate = self
commentTableView.dataSource = 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: "innerCell", for: indexPath) as! commentCell
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
See, the main problem is, both these table views work fine and all, but, in the first chunk of code, if I just do something like,
if tableView == self.tableView{
return cell }
else ...
this won't work, as tableView always seems to be self.tableView.
How can I modify my code so that I can actually impact the text displayed in the inner cell, and the outer cell, in the same block of code?
Also, please note, I know that, based on the example given here, there is no need for these nested cells. I've just simplified the code here to focus on what's important - my actual code has a lot of stuff happening in both the inner and outer cell.
Thank you, any help would be appreciated.

you need to first create two different cell classes.
In outer class :
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SearchPreferredJobTableViewCell
cell.responseCreateBookingObj = { [unowned self] (returnObject) in
DispatchQueue.main.async {
tableView.beginUpdates()
}
// do your logic
DispatchQueue.main.async {
cell.contentView.layoutIfNeeded()
tableView.endUpdates()
} }
return cell
}
// other cell class
Declare variable
var responseCreateBookingObj : APIServiceSuccessCallback?
// send callback from you want to send
guard let callBack = self.responseCreateBookingObj else{
return
}
callBack(true as AnyObject)
// also do in when user scroll it'll manage
tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath){
DispatchQueue.main.async {
tableView.beginUpdates()
}
// do your logic
DispatchQueue.main.async {
cell.contentView.layoutIfNeeded()
tableView.endUpdates()
}
}

Related

Dynamic Table View within Static Table View Cell

I'm fairly new into Swift programming and right now I'm implementing a dynamic table view within a static table view's cell. I know there are plenty of solutions on stackoverflow already but I realised that most of them are in Obj-C which I'm not very familiar with it yet.
Basically, I have a TableView that is set as dynamic in one of the cell of a static table view which is part of the main table view controller. The problem I am having now is there doesn't seem to be a way to implement the data source functions without declaring them for the static table view. I have declared an #IBOutlet for the dynamic table (let's call it dynamicTableView in this scenario).
I have managed to get the override func numberOfSections(in tableView: UITableView) working by returning 1 if the tableView is not dynamicTableView as in the following code:
override func numberOfSections(in tableView: UITableView) -> Int {
if tableView == dynamicTableView {
return data.count
}
else {
return 1
}
}
However, the problem I am having now is implementing the override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath). I have no idea what to be returned if the tableView parameter is not dynamicTableView, but for the static table view.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == dynamicTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamic", for: indexPath) as! dynamicTableViewCell
cell.update(data[indexPath.row]) // A helper function declared in the dynamicTableViewCell.swift
return cell
}
else {
// What to return here?
}
}
Thanks!
Edit: What I meant was I can't seem to have a cellForRowAt data source function that does not affect my static table view.
If there is a value in numberForRows then you have to retutn a cell like this
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == bloggerReviewTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamic", for: indexPath) as! dynamicTableViewCell
cell.update(data[indexPath.row]) // A helper function declared in the dynamicTableViewCell.swift
return cell
}
else {
// What to return here?
let cell = tableView.dequeueReusableCell(withIdentifier: "other", for: indexPath) as! OtherTableCell
return cell
}
}
//
but if the return is zero then there is no need for the if statement inside cellForRowAt as it won't be called for the other table
If the static tableview cells are fairly distinct, they can be individually subclassed.
The dynamic tableview/collectionview can be added in required subclass of static tableview cell.
//class for static tableview
let reviewCellId = "reviewCell"
class StaticTableClass: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//register static cell classes
tableView.register(ReviewCell.self, forCellReuseIdentifier: reviewCellId)
//..
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reviewCellId, for: indexPath)
return cell
}
}
Create a separate ReviewCell class which will contain the dynamic UITableView like so.
This way one class will handle methods of only one tableview.
class ReviewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
lazy var dynamicTableView: UITableView = {
let tv = UITableView()
tv.delegate = self
tv.dataSource = self
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(dynamicTableView)
dynamicTableView.register(UITableViewCell.self, forCellReuseIdentifier: "dynamicCellId")
}
// add further tableview methods in here
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
}
}

How to show UITableView Cell in UITableView without array?

I have this code:
class UserProfilViewController: UIViewController {
// Outlets
#IBOutlet weak var userProfileTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.navigationItem.title = "Profil"
}
}
// MARK: - Table View Data Source
extension UserProfilViewController {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfilCell", for: indexPath)
return cell
}
}
My project in bitbucket: https://bitbucket.org/trifek/karta-nauka/src/master/
I placed one tableviewcell cell on the tableview (UserProfil.storyboard). I have a form on it that I would like to display in this cell. The problem is the cell does not display. Does anyone know how to fix it?
As per the code you have shared, Please change your code to following.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfilCell", for: indexPath) as! UserProfilTableViewCell
return cell
}
Let me know in case of any queries.
IMHO, first try to clear your requirements. If you want to display fix number of cells then you can simply use static cells. If your cells are dynamic i.e their number depends on some calculation or other logic, then you can use dynamic cell. While using dynamic cell, verify if you have registered it or not (if you are using xib for cell) and also verify for the cell identifier.
#Lukasz
Please use the below code for this.
class UserProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setUIComponents()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func setUIComponents(){
registerNibs()
}
}
extension UserProfileViewController: UITableViewDelegate, UITableViewDataSource{
internal func registerNibs(){
tableView.register(UINib(nibName: String(describing: UserProfileTableCell.self), bundle: Bundle.main), forCellReuseIdentifier: kUserProfileCellReuseId)
}
//MARK: TableView Methods -
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sessionCell: UserProfileTableCell.self = tableView.dequeueReusableCell(withIdentifier: kUserProfileCellReuseId, for: indexPath) as! UserProfileTableCell.self
cell.titleLabel.text = “TEST”
return sessionCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class UserProfileTableCell: UITableViewCell {
//Set the "kUserProfileCellReuseId" in nib to register identifier.
let kUserProfileCellReuseId = "kUserProfileCellReuseId"
//MARK: - Override Methods
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
setUIComponents()
}
private func setUIComponents(){
}
}
You never declare that your view controller conforms to the UITableViewDataSource or UITableViewDelegate protocols. Given that you don't do that, you would not be able to set your view controller as the data source or delegate of your table view.

Deleting rows inside tableview while using sender.tag

My tableView cellForRowAtIndexPath looks like so:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CheckoutAppointmentCell.reuseIdentifier) as! CheckoutAppointmentCell
cell.appointment = appointments[indexPath.row]
cell.checkoutButton.tag = indexPath.row
cell.checkoutButton.addTarget(self, action: #selector(checkoutButtonTapped), for: .touchUpInside)
return cell
}
And then I remove the appointment from the tableView and dataSource like so:
func checkoutButtonTapped(sender: UIButton) {
appointments.remove(at: sender.tag)
print(sender.tag)
//self.tableView.beginUpdates()
self.tableView.deleteRows(at: [IndexPath(row:sender.tag, section: 0)], with: .automatic)
//self.tableView.endUpdates()
}
The first time I remove an appointment, it works fine. The sender.tag value is what it should be and the correct row is removed from the tableView.
After removing the first row, it seems to remove the incorrect row afterwards.
I have tried calling reloadData() after calling deleteRows but the animation no longer occurs. beginUpdates() and endUpdates() seems to make no difference neither.
Using tags to track index paths is a common but very poor practice. It fails in any table view that allows rows to be deleted, inserted, or moved because remaining cells now have invalid tags unless the table view is fully reloaded using reloadData.
The better solution that doesn't require the use of reloadData to keep tags up-to-date, is to determine the indexPath of the cell's button based on the button's location.
func checkoutButtonTapped(sender: UIButton) {
let hitPoint = sender.convert(CGPoint.zero, to: tableView)
if let indexPath = tableView.indexPathForRow(at: hitPoint) {
// use indexPath to get needed data
}
}
Well i don't know it is good idea or not but it also seems to work fine using CallBack Closures, as using tags are not good idea.
As some points suggested by #rmaddy I updated answer accordingly.
CustomCell Class-:
import UIKit
class testingCell: UITableViewCell {
var deleteCallBack : ((testingCell)->())?// CallBack function
#IBOutlet weak var parentlabel: UILabel!
#IBAction func deleteButton(_ sender: UIButton) {
// Call your closure
if let callBack = deleteCallBack{
callBack(self)
}
}
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
}
}
Controller class-:
extension ViewController : UITableViewDelegate,UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}// Default is 1 if not implemented
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
// return number of rows in section
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! testingCell
cell.textLabel?.text = data[indexPath.row]
cell.deleteCallBack = { [weak self] tableCell in
//Print indexPath for selected Cell
print(self?.dataTableView.indexPath(for: tableCell) as Any )
if let selectedIndex = self?.dataTableView.indexPath(for: tableCell) {
// Print selected row
print(selectedIndex.row)
// delete row from array
self?.data.remove(at: selectedIndex.row)
// Get index row to be deleted from table
let indePath = NSIndexPath(item: selectedIndex.row, section: selectedIndex.section)
// delete row from table
self?.dataTableView.deleteRows(at: [indePath as IndexPath], with: UITableViewRowAnimation.automatic)
}
}
return cell
}
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat{
// Return cell height
return 100
}
It is indeed printing out correct index after deletion.

Table view cell not showing in xcode

I have this View Controller (see code below) linked to a table view controller storyboard. I get the actual table view showing but not my custom cell. Any ideas of what I am doing wrong?
Good to know:
- I have a separate swift file specifically made for my cell
- I have connected the cell with a cell identifier
- This is not my main view controller.
Thanks in advance!
import UIKit
class ForecastTableViewController: UITableViewController {
var forecasts = [Forecast]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return forecasts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "forecastCell", for: indexPath) as? ForecastCell {
let forecast = forecasts[indexPath.row]
cell.configureCell(forecast: forecast)
return cell
} else {
return ForecastCell()
}
}
}
Add below lines in viewDidload above reload function,
self.tableView.delegate = self
self.tableView.dataSource = self
And no need of self.tableView.reloadData() in viewDidload so remove it!
Put this inside your viewDidLoad() method
self.tableView.registerNib(UINib(nibName: "ForecastCell", bundle: nil), forCellReuseIdentifier: "forecastCell")
Also make sure that you actually set the cell's identifier not restoration ID (common mistake)
Have you register your cell, if not register it first.
tableView.registerClass(MyCell.classForCoder(), forCellReuseIdentifier: kCellIdentifier)
OR if you are using cell with Xib then register
self.tableView.registerNib(UINib(nibName: "UICustomTableViewCell", bundle: nil), forCellReuseIdentifier: "UICustomTableViewCell")
Try to change your code in this way and remember to put your data inside your forecasts array
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "forecastCell", for: indexPath) as? ForecastCell
let forecast = forecasts[indexPath.row]
cell.configureCell(forecast: forecast)
return cell
}

how to expand or replace the cell with another cell, when an particular cell select in table view

I have already asked this doubt/problem in SO. but not get get solution. Please help me out....
i have one table view which will show the list of name data till 10 datas. But what i need is , when user press any cell, that cell should be replace with another cell, which have some image, phone number, same data name. How to do that.
I have two xib : 1. normalcell, 2. expandable/replace cell
Here is my viewconrolelr.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var Resultcount: UILabel!
var tableData = ["thomas", "Alva", "Edition", "sath", "mallko", "techno park",... till 10 data]
let cellSpacingHeight: CGFloat = 5
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var nib = UINib(nibName:"customCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "cell")
Resultcount.text = "\(tableData.count) Results"
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.tableData.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return cellSpacingHeight
}
// Make the background color show through
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.clearColor()
return headerView
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:customCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! customCell
cell.vendorName.text = tableData[indexPath.section]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Starting my cell will look like this :
When i press that cell, i need some thing to do like this with replace ment of like below cell :
But when i press same cell again, again it should go to normal cell.
How to do that ??
First modify your tableView:cellForRowAtIndexPath: implementation as follows. Then you need to implement the click handler. One way would be in the MyCell class. Another would be to override selectRowAtIndexPath. Without knowing more about what you want (e.g. multiple vs single selection), it's hard to give actual code but here's something.
BOOL clickedRows[MAX_ROWS]; // Init this array as all false in your init method. It would be better to use NSMutableArray or something similar...
// selectRowAtIndexPath code
int row = indexPath.row
if(clickedRows[row]) clickedRows[row]=NO; // we reverse the selection for the row
else clickedRows[row]=YES;
[self.tableView reloadData];
// cellForRowAt... code
MyCell *cell = [tableView dequeueResuableCell...
if(cell.clicked) { // Nice Nib
[tableView registerNib:[UINib nibWithNibName... for CellReuse...
} else { // Grey Nib
[tableView registerNib:[UINib nibWithNibName... for CellReuse...
}
You need to create two independent cell on xib. Then you can load using check.You can copy and paste it will work perfectly.
in cellForRowAt like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if selectedIndexPath == indexPath && self.isExpand == true{
let cell = tableView.dequeueReusableCell(withIdentifier: "LeaveBalanceExpandedCell", for: indexPath) as! LeaveBalanceExpandedCell
cell.delegate = self
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "LeaveBalanceNormalCell", for: indexPath) as! LeaveBalanceNormalCell
return cell
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// cell.animateCell(cell)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedIndexPath == indexPath{
if isExpand == true{
self.isExpand = false
}
else{
self.isExpand = true
}
}
else{
selectedIndexPath = indexPath
self.isExpand = true
}
self.tableView.reloadData()
}

Resources