I have multiple cells in my table view; e.g. Cat Cell, Dog Cell, Chicken Cell. In the Cat Cell I have two views: a graphs view and an images view.
If the graphs view is hidden and the images view is visible, then I want to set my cell height to 200, and 350 in the opposite case.
How would I achieve that? I am registering my nib file in the heightForRowAt delegate method of the table view and I have tried different things to no avail. These are the two views in my cell:
#property (weak, nonatomic) IBOutlet UIView *imagesView;
#property (weak, nonatomic) IBOutlet UIView *graphView;
...and this is my heightForRowAt method:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
TrainingImageCell *cell = (TrainingImageCell*)[self.tableView dequeueReusableCellWithIdentifier:#"TrainingImageCell"];
if (cell.isGraphViewOnTop == YES) {
return 350;
}
else {
return 200;
}
return 200;
}
Instead of initiating a new cell, you need to access the one what is already in the tableView.
Instead of
TrainingImageCell *cell = (TrainingImageCell*)[self.tableView dequeueReusableCellWithIdentifier:#"TrainingImageCell"];
Do
TrainingImageCell *currentCell = (TrainingImageCell*)[tableView cellForRowAtIndexPath:indexPath];
And than take a look on the property.
EDIT:
Looking at the lengthy discussion we have, here is a very reliable example for an approach you could use in Swift.
import UIKit
class RightAlignedCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var subLabel: UILabel!
// Your boolean property you would like to map
var randomBool: Bool = false
}
class RightAlignedTableViewController: UIViewController {
// This is just an example for the dataSource, you should have this somewhere already
lazy var dataSource: [Bool] = {
var dataSource: [Bool] = []
for index in 0..<10 {
dataSource.append(index % 2 == 0)
}
return dataSource
}()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
}
extension RightAlignedTableViewController: UITableViewDelegate {}
extension RightAlignedTableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Take a look in the !!!dataSource!!!, what is value for isHidden
let isHidden = self.dataSource[indexPath.row]
// return the right height
return isHidden ? 60 : 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: RightAlignedCell = tableView.dequeueReusableCell(withIdentifier: "RightAlignedCell", for: indexPath) as! RightAlignedCell
cell.titleLabel.text = "Something"
cell.subLabel.text = "Nothing"
cell.randomBool = self.dataSource[indexPath.row]
return cell
}
}
Why are registering nib file in heightForRowAt. You are registering new cell hence cannot access the existing cell. If you want it working in your style instead of registering new cell in height for row at IndexPath you can access existing cell using indexPath as
TrainingImageCell *currentCell = (TrainingImageCell*)[tableView cellForRowAtIndexPath:indexPath];
and access check property.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
TrainingImageCell *currentCell = (TrainingImageCell*)[tableView cellForRowAtIndexPath:indexPath];
if (cell.isGraphViewOnTop == YES) {
return 350;
}
return 200;
}
If you dequeueReusableCellWithIdentifier you just get a new cell, it will not have any data to check if isGraphViewOnTop
My advice will be to return UITableViewAutomaticDimension and set cell views with constants, so when the element is hidden, a cell will have a smaller size.
Related
I am trying to find out if it is possible to subclass TWTRTweetTableViewCell from the TwitterKit library. So far I have my custom cell class inherit from TWTRTweetTableViewCell. The xib has a UIView in it which has an outlet to the cell class and the UIView class is set to
TWTRTweetView. Like this-
class UserTweetViewCell: TWTRTweetTableViewCell {
#IBOutlet weak var tweetViewCustom: TWTRTweetView!
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
}
}
The cell's class in property inspector is set to UserTweetViewCell and the UIVIew's class in the cell is set to TWTRTweetView.
In the main view controller I have this
tableView.register(UserTweetViewCell.self, forCellReuseIdentifier: tweetTableReuseIdentifier)
and then
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tweet = tweetsarr[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: tweetTableReuseIdentifier, for: indexPath) as! UserTweetViewCell
cell.tweetViewCustom.showActionButtons = false
cell.tweetViewCustom.linkTextColor = UIColor(red:0.12, green:0.53, blue:0.90, alpha:1.0)
cell.tweetViewCustom.configure(with: tweet as? TWTRTweet)
cell.tweetViewCustom.theme = .light
cell.tweetViewCustom.delegate = self
return cell
}
However, i get an error at line cell.tweetViewCustom.showActionButtons = false and the error is Unexpectedly found nil while unwrapping an Optional value. What am I missing here?
I finally did it and it's working like a charm. The trick is not to subclass TWTRTweetTableViewCell but instead just subclass a regular UITableViewCell and use a TWTRTweetView inside of it. Which is basically what TWTRTweetTableViewCell does, it has tweetView property which is essentially an IBOutlet of type TWTRTweetView. The custom cell Nib should contain a UIView with TWTRTweetView set as it's class in the identity inspector. Here goes the code-
class CustomTweetCell: UITableViewCell{
#IBOutlet weak var customTweetView: TWTRTweetView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func configureCell(with tweet: TWTRTweet){
self.customTweetView.showActionButtons = false
self.customTweetView.configure(with: tweet)
}
}
For the cell's height, the following needs to be done for the tableview-
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let tweet = tweets[indexPath.row]
let tweetheight = TWTRTweetTableViewCell.height(for: tweet as! TWTRTweet, style: .compact, width: self.view.frame.width, showingActions: false) + 30 //this 30 should be the height of any additional views that you put in the cell Nib file
return tweetheight
}
NOTE: Its extremely important to have autolayout constraints enabled within the tableview cell with the TWTRTweetView and any other views that you may have and also make sure the Table view cell row height is set to Default or blank in the cell's Size inspector.Failing to do so will mess up the tweet view height and will cause undesirable results.
I wanted to Subclass TWTRTweetTableViewCell so that I could add the likes count, retweets count, reply button etc. so far it hasn't worked. So next I am going to give it a try Subclassing TWTRTweetView and use that in the tableview cell instead. I think I have tried it once with partial success. The challenge is the tweet height
This is how I am calculating the tweet height in Objective-c:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
TWTRTweet * tweet = self.tweets[indexPath.row];
if (self.tweets.count > indexPath.row) {
[self.prototypeCell configureWithTweet:tweet];
}
CGFloat tweetHeight = [CustomTweetTableViewCell heightForTweet:tweet style:TWTRTweetViewStyleCompact width:[tableView bounds].size.width showingActions:YES];
self.tweetHeights[indexPath.row] = [NSNumber numberWithFloat:tweetHeight];
return tweetHeight;
}
I have spent days on resolving this issue and after trying much I am asking a question here. I am using a custom UITableViewCell and that cell contains UITextFields. On adding new cells to the table view, the table view behaves abnormal like it duplicates the cell and when I try to edit the textfield of new cell, the textfield of previous cel gets edited too.
The behavior of duplication is as follows: 1st cell is duplicated for 3rd cell. I don't know this is due to reusability of cells but could anyone tell me about the efficient solution?
I am attaching the screenshot of UITableViewCell.
The code for cellForRow is as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.dropDownViewProducts.index = indexPath.row
cell.txtDescription.index = indexPath.row
cell.tfPrice.index = indexPath.row
cell.dropDownQty.index = indexPath.row
cell.tfTotalPrice_Euro.index = indexPath.row
cell.tfTotalPrice_IDR.index = indexPath.row
cell.dropDownViewTotalDiscount.index = indexPath.row
cell.dropDownViewDeposit.index = indexPath.row
cell.tfTotalDeposit_Euro.index = indexPath.row
cell.tfRemaingAfterDeposit_IDR.index = indexPath.row
return cell
}
The issue is the cell is being reused by the UITableView, which is what you want to happen for good scrolling performance.
You should update the data source that supports each row in the table to hold the text the user inputs in the field.
Then have the text field's text property assigned from your data source in cellForRowAt.
In other words, the UITableViewCell is the same instance each time you see it on the screen, and so is the UITextField and therefore so is it's text property. Which means it needs to be assigned it's correct text value each time cellForRowAt is called.
I'm unsure of your code so I have provided an example of how I would do something like what you want:
class MyCell: UITableViewCell {
#IBOutlet weak var inputField: UITextField!
}
class ViewController: UIViewController {
#IBOutlet weak var table: UITableView!
var items = [String]()
fileprivate func setupItems() {
items = ["Duck",
"Cow",
"Deer",
"Potato"
]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupItems()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// the # of rows will equal the # of items
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// we use the cell's indexPath.row to
// to get the item in the array's text
// and use it as the cell's input field text
guard let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as? MyCell else {
return UITableViewCell()
}
// now even if the cell is the same instance
// it's field's text is assigned each time
cell.inputField.text = items[indexPath.row]
// Use the tag on UITextField
// to track the indexPath.row that
// it's current being presented for
cell.inputField.tag = indexPath.row
// become the field's delegate
cell.inputField.delegate = self
return cell
}
}
extension ViewController: UITextFieldDelegate {
// or whatever method(s) matches the app's
// input style for this view
func textFieldDidEndEditing(_ textField: UITextField) {
guard let text = textField.text else {
return // nothing to update
}
// use the field's tag
// to update the correct element
items[textField.tag] = text
}
}
I suggest to do the following
class Product_PriceTableViewCell: UITableViewCell {
var indexRow: Int = -1
func configureCell(index: Int) {
cell.dropDownViewProducts.clean()
...
cell.tfRemaingAfterDeposit_IDR.clean()
}
}
where clean is the function to empty de view (depend on the type)
Then in the delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.configureCell(row: indexPath.row)
return cell
}
As #thefredelement pointed out when the cell is not in the view frame, it is not created. Only when the view is going to appear, it tries to reuse an instance of the cell and as the first is available, the table view uses it but does not reinitialize it. So you have to make sure to clean the data
The rest of the answer is for better coding.
I need to create a view that contains a vertical stack view, which holds a Label, a TableView, another Label, and a Button (in descending order). I have been struggling trying to configure the TableView, as I cannot get it to fill with cells (currently just appears as a blank space in the super view). Right now, I have a ViewController for the main view - 'YourOrderViewController' - and a TableViewController for the TableView - 'OrderItemsTableViewController'. It looks like this:
The main view
class YourOrderViewController: UIViewController{
var cellTitle = String()
var cellSubtitle = String()
#IBOutlet weak var orderListTable: UITableView!
let orderTableController = OrderItemsTableViewController()
override func viewDidLoad() {
super.viewDidLoad()
orderListTable.delegate = orderTableController
orderListTable.dataSource = orderTableController
}
And the TableView subview
class OrderItemsTableViewController: UITableViewController {
var drinkOrderList = [Drink]()
var foodOrderList = [Food]()
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if section == 0 {
return drinkOrderList.count + foodOrderList.count + 1
} else {
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "OrderItemCell", for: indexPath)
if indexPath.row < drinkOrderList.count {
cell.textLabel?.text = drinkOrderList[indexPath.row].drinkName
} else if indexPath.row - drinkOrderList.count < foodOrderList.count {
cell.textLabel?.text = foodOrderList[indexPath.row].foodName
} else {
print("Also here")
cell = tableView.dequeueReusableCell(withIdentifier: "AddToOrderCell", for: indexPath)
}
return cell
}
I initially tried making the whole thing in one view controller, a UIViewController that was the delegate and data source of the UITableView, but that did not work either. Any help is appreciated.
Plain & simple you are over doing it.
For example:
orderListTable.delegate = orderTableController
orderListTable.dataSource = orderTableController
orderTableController instance of OrderItemsTableViewController is a controller type, instead it should an NSObject type of class which conforms to tableView delegate & datasource.
class TableViewDataSource: NSObject, UITableViewDataSouce {
// no need of controller's life cycle
// just declare all & implement all protocol required
}
class TableViewDelegate: NSObject, UITableViewDelegate {
// have some property where you could set the data after you initialize this object
}
Now you could do
let _delegate = TableViewDelegate()
let _dataSource = TableViewDataSource()
_dataSource.someDataProperty = data //<-- important
orderListTable.delegate = _delegate
orderListTable.dataSource = _dataSource
Also, in you controller, you need to add method to reload this tableView
In your cellForRowAt, just use one custom cell for now until you got it working
I have two UITextFields on the UITableViewCell and their IBOutlets are connected in the custom UITableViewCell class called as "CustomCell.swift".
The Enter button is there on the UIView of ViewController and its IBAction is there in the UIViewController class called as "ViewController".
On click of the Enter button I want to see if the two textFields are empty. How do I do it? Please help
create a Bool variable in your class where you have the button action
var isTextFieldTextEmpty: Bool!
then in your table view dataSource method cellForRowAtIndexPath add
if myCell.myTextField.text?.isEmpty == true {
self.isTextFieldTextEmpty = true
} else {
self.isTextFieldTextEmpty = false
}
then in the IBAction of your (Enter) button add
self.myTableView.reloadData()
self.myTableView.layoutIfNeeded()
print(self.isTextFieldTextEmpty)
if all text fields in all cells of the table view have text, it will print false, else if only one text fields among all the text fields has no text, it will print true
Here is a simple solution. It will work for any number of cells.
What you need to do is iterate through the cells and figure out if the textField that particular cell is holding is empty or not. Now the question is how will you iterate through the cells, is there any delegate for that? The answer is No.
You have to manually construct the indexPaths to get the cells from the Table.
Here is a simple walk through. Your set up is quite right. You should have a tableview in your ViewController. So, the IBOutlet of the tableview should be there. I named my TableView "myTableView". And the textField's Outlet should be inside the TableViewCell which is also right. At the end the action method for the Enter button should be in the view controller.
Make sure, you properly connect all the outlets.
Here is the sample custom TableViewCell -
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var internalTextField : UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
}
And now just go to the ViewController.swift-
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var myTableView : UITableView!
var numberOfCells = 2 //you can update it to be any number
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.dataSource! = self //assign the delegate
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfCells
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : CustomTableViewCell = tableView.dequeueReusableCellWithIdentifier("customCell", forIndexPath: indexPath) as! CustomTableViewCell
return cell;
}
#IBAction func pressedEnter(){
var row = 0
while row < numberOfCells { //iterate through the tableview's cells
let indexPath : NSIndexPath = NSIndexPath(forRow: row, inSection: 0) //Create the indexpath to get the cell
let cell : CustomTableViewCell = self.myTableView.cellForRowAtIndexPath(indexPath) as! CustomTableViewCell
if cell.internalTextField.text!.isEmpty{
print("TextField is Cell \(row) is Empty")
}
else{
print("TextField is Cell \(row) is NOT Empty")
}
row += 1
}
}
}
There are comments which explains everything. Hope this helps.
Usually, We use the dequeueReuseableCellwithIdentifier method in ViewController class but I want to use this method in the UITableViewCell.I have tried but I got the exception like this.
fatal error: unexpectedly found nil while unwrapping an optional value
ViewController Class:
#IBOutlet var tableView: UITableView!
var tableData:[songData] = [songData]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.backgroundColor = UIColor.orangeColor()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = TableViewCell()
cell.datas()
return cell
}
}
TableViewCell Class:
#IBOutlet var text1: UILabel!
#IBOutlet var text2: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func datas(){
let vc = ViewController()
let tableData = vc.tableData
print(tableData)
let tableview = vc.tableView
let indexpath:NSIndexPath = NSIndexPath()
let cell = tableview.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexpath) as! TableViewCell //The fatal error is showing exactly at this line.
let artistAndAlbum = tableData[indexpath.row]
cell.text1.text = artistAndAlbum.country
cell.text2.text = artistAndAlbum.currency
tableview.reloadData()
}
I need to customize my table data in the TableViewCell class.If it is possible help me or else why it is not possible?
You're going about this the wrong way. It honestly doesn't make any sense for your table cell subclass to be creating itself. It does make sense, however, for your cell subclass to be passed data and for it to populate itself from that.
You should have your view controller dequeue the cell as normal and then change your table cell function to take some data as a parameter and update itself.
In your view controller:
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerNib(UINib(nibName: "INSERT_NIB_NAME", bundle: nil), forCellReuseIdentifier: "Cell")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
cell.updateWithData(tableData[indexPath.row])
return cell
}
If your cell is a prototype cell in the storyboard then you have to set the reuse identifier there instead of registering in viewDidLoad.
In your table cell:
func updateWithData(artistAndAlbum: songData) {
text1.text = artistAndAlbum.country
text2.text = artistAndAlbum.currency
}
In your view controller's viewDidLoad(), register the class with a reuse identifier.
tableView.registerClass(TableViewCell.self, forCellReuseIdentifier: "ID")
Then your cellForRowAtIndexPath method can dequeue your custom cell.
tableView.dequeueReuseableCellWithIdentifier("ID", indexPath: indexPath)
This isn't just limited to view controllers. If you have a custom table view cell, then register the class for a reuse identifier wherever you setup the table view and then dequeue your custom cell with that identifier in its cellForRowAtIndexPath.
As a general rule of thumb, your view should not keep a reference to its view controller. The view shouldn't care about any view controllers or need to know what the view controller is doing. Either the entire table view and all of its workings should go in your view, hidden from the view controller, or you should keep all of your table view code in the view controller. This will make your life much easier.
Firstly you must set name for cell identifier
after it in cellForRowAtIndexPath method used this code:-
for custom cell
CustomCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CELL" forIndexPath:indexPath];
//-------------------------------------------------------------
for normal cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CELL" forIndexPath:indexPath];
Check your vc.tableView. It's probably nil