How do I animate a UILabel inside a UITableViewCell after reloadData()? - ios

I'm creating a form and whenever a user forgets to fill out a form field, I have a mechanism to highlight the required text labels for any of the cells in which the user did not select a value in. After the color change is reflected and the tableView is reloaded, I want to animate the highlighted label(s) also. What is the proper way to achieve this? The tableView can load a mixture of custom tableViewCells, so I want to know this can be done across all the custom cells.
Please note: Labels are highlighted in red if missing on "Submit" button click. As the missing fields are filled out, the labels are no longer highlighted as soon as they have a value assigned to that field. So now I need a way to animate the individual labels on the cells that are of different custom cell classes only when the user attempts to click the submit button with missing fields

Protocols
You can create a protocol that your custom cells will implement, so in each custom cell you can make your own implementation of a function that you will call in the delegate method from the table view:
tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
guard let customCellThatImplementsFormCell = cell as? FormCell else { return }
/// check if has errors and call either function
}
depending on if your cell has an error or not
this is a very simple idea but it could look like this
protocol FormCell {
func showError()
func hideError()
}
class FormTextInputCell: UITableViewCell {
}
extension FormTextInputCell: FormCell {
func showError() {
// Style the cell and animate what you want
}
func hideError() {
// Style the cell and animate what you want
}
}

Related

Swift tableview cell radio button implementation

I need to place a radio button in tableview custom cell. whenever user clicks the tableview cell or button then radio button needs to work. I tried by using below code but didn't execute well.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! TableViewCell
cell.country.text = self.animals[indexPath.row]
cell.selectionStyle = UITableViewCellSelectionStyle.none;
if selectedRows.contains(indexPath)
{
cell.radioButton.setImage(UIImage(named:"check.png"), for: .normal)
}
else
{
cell.radioButton.setImage(UIImage(named:"uncheck.png"), for: .normal)
}
return cell
}
Here's a great solution for creating radio buttons in a UITableView using a storyboard that requires zero code - and has 2 great Cool Tips!!
Make sure your table view is set to Single Selection, and to use Static cells.
Add a Basic cell, set the image to be your unchecked button image, and make sure the selection style is Default
Cool Tip # 1: Click on and select the cell's image view, and then set it's highlighted image to be your checked state. When the cell is highlighted or selected, the image view within will change to show its highlighted state.
Cool Tip # 2: Next, drag a UIView into the cell's content view, behind the text label. As you're using a basic cell, you won't be able to drop it directly into the cell, you'll need to drag it into onto the Document Outline on the left instead. Then hook this up to the cell's selected background view outlet. When a cell is selected (or highlighted), this view will be displayed in the background. In this case, we're going to use it to prevent the grey background appearing, so set its colour to Clear. Note that it doesn't matter what size the view is, and there's no need to set any constraints - it's automatically sized to match the cell at runtime.
Finally, duplicate this cell and change the text for each of your radio button options. Build and run, and you have code-free radio buttons!
In your TableViewCell class why don't you create a data source element and override the didSet for it. also in your data source for the UITableView I would recommend an array of something more than just a String.
I haven't compiled the below so this is just an idea.
import UIKit
class TableViewCell : UITableViewCell {
var data: Animal? {
didSet {
self.country.text = data.description
if (data.isSelected) {
self.radioButton.setImage(UIImage(named:"check.png"), for: .normal)
} else {
self.radioButton.setImage(UIImage(named:"uncheck.png"), for: .normal)
}
}
}
}
in your view controller you will of course have to set the isSelected property whenever a row is tapped.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var animal = self.animals[indexPath.row]
animal.isSelected = !animal.isSelected
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! TableViewCell
cell.data = self.animals[indexPath.row]
}
and for your Animal maybe something like this:
struct Animal {
var description: String
var isSelected: Bool
}

UITableViewCell styling resetting when scrolled off and back onto view

I currently have a TableView in my project, which is set up to turn a cell green when it is pressed, and back to clear if it is pressed a second time. However, if I scroll down to the bottom of the table view, and scroll back up, all my cells have been reset to their default clear colour.
I'm not sure how to go about fixing this issue, as anything I can find referring to it is in Objective-C rather than Swift. Any help and advice as to how to go about this would be great, thanks.
Everytime a UITableViewCell goes out of the screen, any function that you've written in the tableViewController/ViewController runs again.
for example in cellForRowAtIndexPath if you have a cell.setUpCell() or something similar, it will rerun and reset your values to the original values.
if you have a
var name = testName in your MainVC
and you update something in your cell, you should change the name in your mainVc too.
Every time you scroll or call tableView.reloadData() UITableView cells will reload. So, every time you select UITableViewCell, add selected index (indexPath.row) to an array(ex: selectedIndexArray) in your didSelectRowAt indexPath: delegate. If the cell you selected is already selected one, then remove the cell from selectedIndexArray.
And in your cellForRowAt indexPath: manage the cells using selectedIndexArray.
var selectedIndexArray:[Int] = [] //to save selected tableViewCells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let isSelected = false
for each in selectedIndexArray
{
if each == indexPath.row
{
isSelected = true
}
}
if isSelected == true
{
//set selected cell color
}
else
{
//set default cell color
}
}
You need to write the logic of adding and removing cell indexes in your didSelectRowAt indexPath:.

Handling touch events to one UITableCell

I have some UITableViewCells in which I can perform click actions.
When a click action starts the cell will expand and a UIPickerView, UITextView or other data will be displayed. (See example images)
When you click the TableCell again the cell will collapse and the original state will be displayed.
Currently every cell expands when I click in a UITableViewCell with an expand action. When I click on a cell I want every other cell to be collapsed and only expand the clicked one.
Question:
How can I give my table cell, which is expanded, a state in which it will receive all touch events. (Become the first responder for the whole screen) and close it first and after the close action send the click event to the corresponding UITableViewCell.
I have made the UITextView first responder and that will close the keyboard after it's poped up, but I want the table cell to be the handler of the click events.
Example code
func togglePicker() {
//This function is called when the UITableCell is clicked.
canBecomeFirstResponder()
// Some code here which adds UIPickerView, UITextView or other data.
setNeedsLayout()
}
I tried this code, but this cell only receives touch events which are triggered in this cell and not outside its boundaries.
Example images
Orginal cell state
First cell is expanded
I implemented the following solution:
class CustomCell: UITableViewCell {
var delegate: CustomCellDelegate?
var focussed: Bool = false
function becomeFocussed() {
//Put some code here to change the design
setNeedsLayout()
focussed = true
delegate?.isFocussed(self)
}
function becomeNormaleState() {
//Put some code here to change the design to the original state.
focussed = false
setNeedsLayout()
}
}
Every Cell has two functions: becomeFocussed() and becomeNormaleState(). When the cell is clicked the function becomeFocussed() should be called.
This functions tells the delegate that the cell is selected.
In the TableViewController the function isFocussed(cell : UITableViewCell), loops through al cells of the current tableView en calls "becomeNormaleState()" for every cells which is focussed.
okay, by looking at the images u are changing the frame of the cell, one solution for When I click on a cell I want every other cell to be collapsed and only expand the clicked one. as u asked in the question, u can store the index path of the clicked cell and use this collapse other cell if that cell's picker view is showing for example,
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if showIndexPath?.row == indexPath.row
{
return 330 //expanded height for example
}
else {
return 100 //normal state height for example
}
}
//in this method u can decide which one to expand or collapse
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//if user selected the same cell again close it
if showIndexPath?.row == indexPath.row
{
//already expanded, collapse it
showIndexPath = nil
tableView .reloadRowsAtIndexPaths([showIndexPath!], withRowAnimation: .Fade)
}
else
{
//expand this cell's index path
showIndexPath = indexPath
tableView .reloadRowsAtIndexPaths([showIndexPath!], withRowAnimation: .Fade)
}
}

Find Out Which Section Button Belongs To In UITableView When Clicked Inside Cell

I am trying to change the value of an element of an array depending which section a button is clicked in. For example say I have this array numbers = [0,0,0,0,0] and I want to change the first element to 5. I insert 5 into the cell of the first section and click done in that same section and the array will now read [5,0,0,0,0]. Is there a way to know which section the button belongs to?
Right now I have two separate classes. One for the custom cell and one for the tableview. Both of the have an outlet to the button. When the button is clicked the custom cell class changes a temporary global number to the inserted number. And inside the table class I want the button action to take that global number and insert in into the element that is the same number as the section the button belongs to. Except I don't know how to find out which section it belongs to.
Can anyone help me out with this? I'm writing in Swift btw.
If you only need the section (not the section and row) of the index path of the cell containing the tapped button, you could tag the button with the section number (Int) when you configure the table view cell:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("MyIdentifier", forIndexPath:indexPath) as! MyCustomTableViewCell
cell.myCustomButton.tag = indexPath.section
// Remove all existing targets (in case cell is being recycled)
cell.myCustomButton.removeTarget(nil, action: nil, forControlEvents: .AllEvents)
// Add target
cell.myCustomButton.addTarget(self, action:"buttonAction:", forControlEvents:.TouchUpInside)
return cell
}
func buttonAction(sender:AnyObject)
{
if let button = sender as UIButton{
println("Tapped Button in section: \(button.tag)")
}
}
If you need both the section AND the row, you're better off storing the whole index path. For that, you can either use a custom UIButton subclass with a property of type NSIndexPath, or store it in the table view cell.
But the second solution is a bit messy since you have to access the table cell (and then, the index path) from the button by using superview, etc., and this relies on the table cell's subview structure. I'm not sure if the parent view of your button is the table cell itself, or its "content view" (I'm a bit rusty right now...)
If you want to find which cell has been 'clicked' you could use the UITableViewDelegate method.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}

Reordering UITableView without reorder control

I need the user to be able to reorder a UITableView by this way: he touches a cell for a predetermined period (e.g. 1 second), then he can drag and drop it over the other cells.
I know how to implement the 'long touch' detection using a gesture recognizer, but what is the best way to implement the drag and drop ability without using a reorder control (the user should drag the cell from anywhere in the cell, not only from the reorder control)?
This is an old question, but here's a solution that's tested and working with iOS 8 through 11.
In your UITableViewCell subclass try this:
class MyTableViewCell: UITableViewCell {
weak var reorderControl: UIView?
override func layoutSubviews() {
super.layoutSubviews()
// Make the cell's `contentView` as big as the entire cell.
contentView.frame = bounds
// Make the reorder control as big as the entire cell
// so you can drag from everywhere inside the cell.
reorderControl?.frame = bounds
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: false)
if !editing || reorderControl != nil {
return
}
// Find the reorder control in the cell's subviews.
for view in subviews {
let className = String(describing: type(of:view))
if className == "UITableViewCellReorderControl" {
// Remove its subviews so that they don't mess up
// your own content's appearance.
for subview in view.subviews {
subview.removeFromSuperview()
}
// Keep a weak reference to it for `layoutSubviews()`.
reorderControl = view
break
}
}
}
}
It's close to Senseful's first suggestion but the article he references no longer seems to work.
What you do, is make the reorder control and the cell's content view as big as the whole cell when it's being edited. That way you can drag from anywhere within the cell and your content takes up the entire space, as if the cell was not being edited at all.
The most important downside to this, is that you are altering the system's cell view-structure and referencing a private class (UITableViewCellReorderControl). It seems to be working properly for all latest iOS versions, but you have to make sure it's still valid every time a new OS comes out.
I solved the question of the following steps:
Attach gesture recognizer to UITableView.
Detect which cell was tapped by "long touch". At this moment create a snapshot of selected cell, put it to UIImageView and place it on the UITableView. UIImageView's coordinates should math selected cell relative to UITableView (snapshot of selected cell should overlay selected cell).
Store index of selected cell, delete selected cell and reload UITableView.
Disable scrolling for UITableView. Now you need to change frame of snapshot UIImageView when you will drag cell. You can do it in touchesMoved method.
Create new cell and reload UITableView (you already have stored index) when the user finger leaves screen.
Remove the snapshot UIImageView.
But it was not easy to do it.
The article Reordering a UITableViewCell from any touch point discusses this exact scenario.
Essentially you do the following:
Find the UITableViewCellReorderControl (a private class).
Expand it so it spans the entire cell.
Hide it.
The user will now be able to drag the cell from anywhere.
Another solution, Cookbook: Moving Table View Cells with a Long Press Gesture, achieves the same effect by doing the following:
Add a long press gesture recognizer on the table view.
Create a snapshot of the cell when the cell is dragged.
As the cell is dragged, move the snapshot around, and call the -[UITableView moveRowAtIndexPath:toIndexPath:].
When the gesture ends, hide the cell snapshot.
For future reference...
I had the same problem, I found another question(Swift - Drag And Drop TableViewCell with Long Gesture Recognizer) about it and someone suggested this tutorial: https://www.freshconsulting.com/create-drag-and-drop-uitableview-swift/
worked just perfectly for me
I know this is a question about UITableView. But I ended with a solution of using UICollectionView rather than UITableView to implement longtap reorder. Its easy and simple.
tableView.dragInteractionEnabled = true
tableView.dragDelegate = self
tableView.dropDelegate = self
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { }
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession,
at: indexPath: IndexPath) -> [UIDragItem] {
return [UIDragItem(itemProvider: NSItemProvider())]
}
func tableView(_ tableView: UITableView, dropSessionDidUpdate session:
UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
if session.localDragSession != nil {
return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
return UITableViewDropProposal(operation: .cancel, intent: .unspecified)
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
}

Resources