How to access UITableView from within UITableViewCell in Swift - ios

I have TableView with cells and I have button on each cell. I want to add some functionality - when user presses button in cell some request is made to server and cell disappears from TableView.
How i can perform this? I know method to delete cell from tableView but to use it i need to get access to tableView from it's cell.

One way could be to add a callback closure to a custom cell and execute it upon cell selection. your view controller would hook this callback up in tableView(_,cellForIndexPath:) or tableView(_, cellWillAppear:)
import UIKit
class SelectionCallbackTableViewCell: UITableViewCell {
var selectionCallback: (() -> Void)?
#IBOutlet weak var button: UIButton!
#IBAction func buttonTapped(sender: UIButton) {
self.selectionCallback?()
}
override func layoutSubviews() {
super.layoutSubviews()
self.contentView.addSubview(self.button)
}
}
register the cell for the tableview. I did it in the storyboard.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var array = [1,1]
override func viewDidLoad() {
super.viewDidLoad()
for x in stride(from: 2, through: 30, by: 1){
let i = array[x-2]
let j = array[x-1]
array.append(i+j)
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! SelectionCallbackTableViewCell
cell.selectionCallback = {
println("\(self.array[indexPath.row]) at \(indexPath.row) on \(tableView) selected")
}
cell.textLabel?.text = "\(self.array[indexPath.row])"
return cell
}
}
Now implement the callback closure as needed
cell.selectionCallback = {
println("\(self.array[indexPath.row]) at \(indexPath.row) on \(tableView) selected")
}
will result in logs like
832040 at 29 on <UITableView: 0x7fe290844200; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x7fe2907878e0>; layer = <CALayer: 0x7fe290711e60>; contentOffset: {0, 697}; contentSize: {375, 1364}> selected
all possible helpful information are present:
indexPath
datasource object
table view
cell
if you want delete the cell after successfully informing your server, for a simple solution do:
cell.selectionCallback = {
println("\(self.array[indexPath.row]) at \(indexPath.row) on \(tableView) selected")
self.apiManager.deleteObject(obj, success: { response in
self.array.removeAtIndex(indexPath.row)
tableView.reloadData()
}, failure:{ error in
// handle error
})
}
Disclaimer:
Personally I don't recommend to implement datasources as UIViewController. It violates the SOLID principles.
Instead datasources should reside in their own classes.
For my own Projects I use an architecture I call "OFAPopulator", that allows me to create independent datasources for each section of a table or collection view.
I chose to implement it inside the view controller here for brevity.
I missed the button part. edited.
Example code: https://github.com/vikingosegundo/CellSelectionCallback

Related

Multiple collectionViews in single TableCell

I am planning to take multiple collectionView inside single tableView Cell.
What I really want is to show album photos in this manner (UI is important). See image:
how can make this UI ?
Till now I have take a TableView -> TableCell (Single) -> 3 different collectionView inside cell.
Code:
CustomGalleryController
class CustomGalleryController: UIViewController {
var arrayOfPHAsset = [PHAsset]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
CustomAlbum().fetchCustomAlbumPhotos { (array) in
self.arrayOfPHAsset = array
CustomGalleryTableCell().firstCollectionArray = array
CustomGalleryTableCell().secondCollectionArray = array
CustomGalleryTableCell().thirdCollectionArray = array
//self.collectionView.reloadData()
}
}
extension CustomGalleryController : UITableViewDelegate{
}
extension CustomGalleryController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomGalleryTableCell", for: indexPath) as! CustomGalleryTableCell
return cell
}
}
CustomGalleryTableCell
class CustomGalleryTableCell: UITableViewCell {
#IBOutlet var firstCollectionView: UICollectionView!
#IBOutlet var secondCollectionView: UICollectionView!
#IBOutlet var thirdCollectionView: UICollectionView!
var firstCollectionArray = [PHAsset]()
var secondCollectionArray = [PHAsset]()
var thirdCollectionArray = [PHAsset]()
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
}
}
extension CustomGalleryTableCell : UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// if collectionView == firstCollectionView {
// return 1;//firstCollectionArray.count
// }else if collectionView == secondCollectionView {
// return 1;//secondCollectionArray.count
// }else{
// return 1;//thirdCollectionArray.count
// }
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == firstCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FirstCollectionCell", for: indexPath) as! FirstCollectionCell
return cell
}else if collectionView == secondCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SecondCollectionCell", for: indexPath) as! SecondCollectionCell
return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ThirdCollectionCell", for: indexPath) as! ThirdCollectionCell
return cell
}
}
}
But getting crash:
Terminating app due to uncaught exception NSInvalidArgumentException',
reason: '-[UITableView collectionView:numberOfItemsInSection:]:
unrecognized selector sent to instance 0x1049ed600
po 0x1049ed600 gives:
<UITableView: 0x1049ed600; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x17424b550>;
layer = ; contentOffset: {0, 0}; contentSize:
{414, 44}>
Please suggest any other and good way to achieve this UI?
What may be the cause of crash if my way of making this UI is correct?
This would achievable using the CHTCollectionViewWaterfallLayout made by chiahsien. I already used this library and I must say it works really great.
You can install it using pods:
pod 'CHTCollectionViewWaterfallLayout/Swift'
The Demo in Swift can be found here.
The only thing different from this example is that you have a certain padding on each column on the top, but I'm sure this is achievable also.
Please suggest any other and good way to achieve this UI?
If you are aiming to achieve what are you mentioning the in screenshot, there is no need to implement it as a tableview which contains collectionviews, instead, the straightforward way to work with UICollectionViewLayout.
As a tip, for the purpose of saving some time and effort, you could find a library that can do the job for you, such as:
CHTCollectionViewWaterfallLayout.
Or if you are interested in reordering the items:
RAReorderableLayout.
Beginner with swift and Xcode here. I've been looking for a solution to populate two CollectionView in a single TableViewCell. Even though this might not exactly be appropriate for your case, the title of your question matches :-) Finally managed to do it by using the tag attribute in the storyboard.
Here's how I did it:
First give a tag number to each CollectionView in the storyboard
In your ViewControllerDataSource, check if it's the right CollectionView by using an if statement and return whatever's needed (numberOfItemsInSection, cellForItemAt).
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView.tag == 0 {
return imageArray1.count
} else {
return imageArray2.count
}
}
}
Hope this helps!

UITableView Duplicate cells (custom cells with textfields)

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.

How do I fill a UITableView that is a subview of a UIViewController?

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

How do I get the values from Custom UITableViewCell to ViewController?

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.

Change UIView height with respect to UITableview

I have a View named AlarmView and a tableview named alarmtableview inside AlarmView , also I have a textfield
Now What I want is when a user enters any number in textfield , tableview will be reloaded with number of rows equal to that number entered by the user.
I have set the AlarmView height as 0 initially , so that when the textfield is empty AlarmView remains invisible from the users.
And after reloading the alarmtableview , I am updating the detailview's height equal to tableview's height.
But when I run the app the view's height is not updated. it remains invisible from the users even after reloading the tableview
Below Is the code I have written to achieve the same
#IBOutlet weak var AlarmViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var AlarmView: UIView!
#IBOutlet weak var AlarmTableView: UITableView!
#IBOutlet weak var FrequencyField: CustomUITextField!
func textFieldDidEndEditing(textField: UITextField) {
print("textFieldDidEndEditing called")
if textField === FrequencyField{
self.AlarmToggleAction(self.AlarmSwitch)
}
}
//MARK: ALARM Actions
#IBAction func AlarmToggleAction(sender: UISwitch) {
if sender.on{
if let count = Int(self.FrequencyField.text!) {
print("ON state")
alarmCount = count
AlarmTableView.reloadData()
print("AlarmTableView.frame.height : \(AlarmTableView.frame.height)")
AlarmViewHeightConstraint.constant = AlarmTableView.frame.height
}
}
else{
print("OFF state")
alarmCount = 0
//AlarmTableView.reloadData()
//AlarmViewHeightConstraint.constant = 0
}
}
//MARK: Tableview Actions
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRowsInSection called alarmCount : \(alarmCount)")
return alarmCount
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("cellForRowAtIndexPath called")
let cell = tableView.dequeueReusableCellWithIdentifier("AlarmCell") as! AlarmCell
cell.TimeLabel.text = "Alarm \(indexPath.row + 1)"
cell.TimeField.tag = indexPath.row
cell.TimeSwitch.tag = indexPath.row
return cell
}
Please help me out of this problem.
Thanks in advance
Try using contentSize instead of frame size like:
AlarmViewHeightConstraint.constant = AlarmTableView.contentSize.height

Resources