How to animate UITableViewCell using Swift? - ios

I have a UITableView and inside the tableViewCell I have a UICollectionView.
My requirement is while tapping on a button of first tableView cell I have to animate second tableViewCell.
Below is my code :-
//Cell For Row at indexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (0 == indexPath.section) {
let cell = tableView.dequeueReusableCell(withIdentifier: "FirstRowCell") as! FirstRowCell
cell.btnReview.addTarget(self, action: #selector(GotoReview), for: UIControlEvents.touchUpInside)
cell.btnMyProduct.addTarget(self, action: #selector(AnimateCollectionView), for: UIControlEvents.touchUpInside)
//cell.
return cell
} else if ( 1 == indexPath.section) {
let identifier = "TableCollectionCell"
var tableCollectionCell = tableView.dequeueReusableCell(withIdentifier: identifier) as? TableCollectionCell
if(tableCollectionCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("TableCollectionCell", owner: self, options: nil)!
tableCollectionCell = nib[0] as? TableCollectionCell
tableCollectionCell?.delegate = self
}
return tableCollectionCell!
} else {
let identifier = "BrandImagesTableCell"
var brandImagesTableCell = tableView.dequeueReusableCell(withIdentifier: identifier)
if(brandImagesTableCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("BrandImagesTableCell", owner: self, options: nil)!
brandImagesTableCell = nib[0] as? BrandImagesTableCell
}
//brandImagesTableCell.
return brandImagesTableCell!
}
}
In my code you can see:
if (0 == indexPath.section)
In that I have a button target (#selector(AnimateCollectionView)).
I want to animate tableCollectionCell which is at (1 == indexPath.section).
See my AnimateCollectionView method :-
func AnimateCollectionView() {
let identifier = "TableCollectionCell"
var tableCollectionCell = tableView.dequeueReusableCell(withIdentifier: identifier) as? TableCollectionCell
if(tableCollectionCell == nil) {
let nib:Array = Bundle.main.loadNibNamed("TableCollectionCell", owner: self, options: nil)!
tableCollectionCell = nib[0] as? TableCollectionCell
tableCollectionCell?.delegate = self
}
tableCollectionCell?.alpha = 0
UIView.animate(withDuration: 1.50, animations: {
//self.view.layoutIfNeeded()
tableCollectionCell?.alpha = 1
})
}

If you want to animate its change, you can just change some state variables, call tableView.reloadRows(at:with:), and have your cellForRowAt then check those state variables to know what the final cell should look like (i.e. whether it is the "before" or "after" cell configuration).
For example, here is an example, with two cells, toggling the second cell from red to blue cells.
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "RedCell", bundle: nil), forCellReuseIdentifier: "RedCell")
tableView.register(UINib(nibName: "BlueCell", bundle: nil), forCellReuseIdentifier: "BlueCell")
}
#IBAction func didTapButton(_ sender: UIButton) {
isRedCell = !isRedCell
tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: .fade)
}
var isRedCell = true
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCell", for: indexPath)
return cell
} else if indexPath.row == 1 {
if isRedCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RedCell", for: indexPath)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "BlueCell", for: indexPath)
return cell
}
}
fatalError("There are only two rows in this demo")
}
}
Now, assuming you really needed to use NIBs rather than cell prototypes, I would register the NIBs with the tableview like above, rather than having cellForRow have to manually instantiate them itself. Likewise, for my cell with the button, I just used a prototype cell in my storyboard and hooked the button directly to my #IBOutlet, avoiding a NIB for that cell entirely.
But all of that is unrelated to your main problem at hand: You can create your cells however you want. But hopefully this illustrates the basic idea, that on the tap of the button, I'm not trying to load any cells directly, but I just update some status variable that cellForRow will use to know which cell to load and then tell the tableView to animate the reloading of that IndexPath.
By the way, I assumed from your example that the two different potential cells for the second row required different NIBs. But if you didn't, it's even easier (use just one NIB and one cell identifier), but the idea is the same, just update your state variable and reload the second row with animation.

Related

How do I use tableView.indexPathForRow(at: touchPoint) with sections

I use sections to load messages(viewForFooterInSection) and rows to load the reply of specific messages if any.
Previously I was using a long press gesture on the tableView to detect a touch on the tableView and return the indexPath using tableView.indexPathForRow(at: touchPoint), however I have not found a similar method to get indexPath of long pressed cell
Can anyone help?
I am not sure why you are going for cell-level gesture when you have already achieved getting indexPath using gesture on tableview. In case you are trying to get cell from indexPath then you can try like
guard let cell = tableView.cellForRow(at: indexPath) else { return }
Anyhow coming to answer for your question, we can do the following way to get indexPath from cell-level.
protocol CustomCellDelegate: AnyObject {
func longPressAction(onCell: CustomCell)
}
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
let lg = UILongPressGestureRecognizer(target: self, action: #selector(longPress))
lg.minimumPressDuration = 0.5
lg.delaysTouchesBegan = true
self.addGestureRecognizer(lg)
}
#objc func longPress(gestureReconizer: UILongPressGestureRecognizer) {
if gestureReconizer.state != UIGestureRecognizer.State.ended {
return
}
delegate?.longPressAction(onCell: self)
}
}
And in your tableview cell for row method, assign the delegate.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as? CustomCell else { return UITableViewCell() }
cell.delegate = self
return cell
}
And confirm to the CustomCellDelegate protocol in your viewController.
extension ViewController: CustomCellDelegate {
func longPressAction(onCell: CustomCell) {
guard let indexPath = tableView.indexPath(for: onCell) else { return }
print(indexPath.section, indexPath.row)
}
}

SDWebImage not loading images Until I scroll the tableView in Swift

If I scroll, then only the images are loading. Here is my code
var nib: [Any] = Bundle.main.loadNibNamed("SampleTableViewCell", owner: self, options: nil)!
let cell = nib[0] as? SampleTableViewCell
let values = detailsArr[indexPath.row] as! SampleModel
let url = URL(string: values.imageStr)
cell?.title_Lbl.text = values.title
cell?.desccription_Lbl.text = values.description
cell?.image_View.sd_setImage(with: url)
cell?.layoutIfNeeded()
tableView.estimatedRowHeight = 370
return cell!
}
You need to register the nib as a reusable cell for the UITableView:
let sampleNib = UINib(nibName: "SampleTableViewCell", bundle: nil)
tableView.register(sampleNib, forCellReuseIdentifier: "SampleTableViewCell")
Then in tableView's dataSource method cellForRowAt deque it:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sampleCell = tableView.dequeueReusableCell(withIdentifier: "SampleTableViewCell") as! SampleTableViewCell
/*
Customise your cell here.
I suggest you make all your customisations inside the cell, and call here a function from the cell
eg.:
let values = detailsArr[indexPath.row] as! SampleModel
sampleCell.setupCell(with: values)
*/
return sampleCell
}

How to handle two table views in single view controller by using xibs as cell

I am trying to use the segment controller with 3 segments. If I click on first segment, I should get one tableview. When I click on second segment, I should get second tableview and for third segment I should get 3rd table view. Here I am using XIB's for tableview cell. I tried something but I am not getting any data in the table. The table is loading. But the cell is not loading. I am giving my code below. If any one helps me, would be very great. Thanks in advance.
var arr1 = ["1","2","3","4"]
var imagesarray = [UIImage(named: "11.png")!, UIImage(named: "22.png")!, UIImage(named: "33.png")!,UIImage(named: "11.png")!,UIImage(named: "22.png")!, UIImage(named: "33.png")!]
override func viewDidLoad() {
super.viewDidLoad()
view_track.isHidden = false
view_watch.isHidden = true
view_ebooks.isHidden = true
table_track.register(UINib(nibName: "ProgramMListenTableViewCell", bundle: nil), forCellReuseIdentifier: "ProgramMListenTableViewCell")
table_watch.register(UINib(nibName: "ProgramMWatchTableViewCell", bundle: nil), forCellReuseIdentifier: "ProgramMWatchTableViewCell")
table_watch.register(UINib(nibName: "ProgramEbooksTableViewCell", bundle: nil), forCellReuseIdentifier: "ProgramEbooksTableViewCell")
table_ebooks.delegate = self
table_ebooks.dataSource = self
table_track.delegate = self
table_track.dataSource = self
table_watch.delegate = self
table_watch.dataSource = self
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
#IBAction func Segment(_ sender: Any) {
switch segment_program.selectedSegmentIndex
{
case 0:
view_track.isHidden = false
view_watch.isHidden = true
view_ebooks.isHidden = true
self.table_track.reloadData()
break
case 1:
view_track.isHidden = true
view_watch.isHidden = false
view_ebooks.isHidden = true
self.table_watch.reloadData()
name_program.text = "Videos"
break
case 2:
view_track.isHidden = true
view_watch.isHidden = true
view_ebooks.isHidden = false
self.table_ebooks.reloadData()
name_ebooks.text = "Ebooks"
break
default:
break
}
}
}
extension ProgramMListenViewController: UITableViewDataSource,UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == table_track{
return self.arr1.count
}else if tableView == table_watch{
return self.imagesarray.count
}else{
return self.arr1.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == table_track{
let cell = table_track.dequeueReusableCell(withIdentifier: "ProgramMListenTableViewCell") as! ProgramMListenTableViewCell
}else if tableView == table_watch{
let cell = table_watch.dequeueReusableCell(withIdentifier: "ProgramMWatchTableViewCell") as! ProgramMWatchTableViewCell
cell.img_watch.image = imagesarray[indexPath.row]
}else if tableView == table_ebooks{
let cell = table_ebooks.dequeueReusableCell(withIdentifier: "ProgramEbooksTableViewCell") as! ProgramEbooksTableViewCell
cell.image_ebooks.image = imagesarray[indexPath.row]
}
return UITableViewCell()
}
}
You have to return your cell, instead of always returning UITableViewCell()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == table_track{
let cell = table_track.dequeueReusableCell(withIdentifier: "ProgramMListenTableViewCell") as! ProgramMListenTableViewCell
return cell
}else if tableView == table_watch{
let cell = table_watch.dequeueReusableCell(withIdentifier: "ProgramMWatchTableViewCell") as! ProgramMWatchTableViewCell
cell.img_watch.image = imagesarray[indexPath.row]
return cell
}else if tableView == table_ebooks{
let cell = table_ebooks.dequeueReusableCell(withIdentifier: "ProgramEbooksTableViewCell") as! ProgramEbooksTableViewCell
cell.image_ebooks.image = imagesarray[indexPath.row]
return cell
}
return UITableViewCell()
}
As mentioned before you need to return the respective UITableViewCell, depending on the table in order for it to be shown. You should watch for errors in dequeueReusableCell(), thus I recommend the following implementation:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var returnCell = UITableViewCell()
if tableView == table_track{
guard let cell = table_track.dequeueReusableCell(withIdentifier: "ProgramMListenTableViewCell") as? ProgramMListenTableViewCell else {
print("Error getting cell as ProgramMListenTableViewCell")
return returnCell
}
/*Customize your cell*/
returnCell = cell
}else if tableView == table_watch{
guard let cell = table_watch.dequeueReusableCell(withIdentifier: "ProgramMListenTableViewCell") as? ProgramMWatchTableViewCell else {
print("Error getting cell as ProgramMWatchTableViewCell")
return returnCell
}
/*Customize your cell*/
cell.img_watch.image = imagesarray[indexPath.row]
returnCell = cell
}else if tableView == table_ebooks{
guard let cell = table_ebooks.dequeueReusableCell(withIdentifier: "ProgramMListenTableViewCell") as? ProgramEbooksTableViewCell else {
print("Error getting cell as ProgramEbooksTableViewCell")
return returnCell
}
cell.image_ebooks.image = imagesarray[indexPath.row]
returnCell = cell
}
return returnCell
}
This way you have a safe way of getting the correct type of your cell, editing it and returning it to the corresponding table.

Swift Change label text color on tap from within TableViewCell

I have a UILabel that is inside a TableView, I want to change the color of the UILabel to red on user tap. I am using a UITapGestureRecognizer and on tapping the UILabel I can get the content of the UILabel but I can't get the actual UILabel since to my knowledge you can't have parameters inside a UIGesture function.
This is my code and it will help clear things up
class HomeProfilePlacesCell: NSObject {
var Post = [String]()
#objc func PostTap(_ sender: UIGestureRecognizer) {
print(Post[(sender.view?.tag)!])
}
func HomeProfilePlaceTVC(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, streamsModel : streamModel,HOMEPROFILE: HomeProfile, controller: UIViewController) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTVC", for: indexPath) as! HomeTVC
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PostTap(_:)))
tapGesture.delegate = self as? UIGestureRecognizerDelegate
cell.post.addGestureRecognizer(tapGesture)
cell.post.text = streamsModel.Posts[indexPath.row]
cell.post.tag = indexPath.row
Post = streamsModel.Posts
return cell
}
}
My function there is PostTap whenever a user taps the UILabel which is the cell.post then I can read it's content inside PostTap but in order to change the color of that UILabel then I'll have to pass the let cell constant into the PostTap function.
Is there anyway I can do that or a work around ? I am new to Swift
Use TableView Delegates: [SWIFT 4.0]
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let cell = tableView.cellForRowAtIndexPath(indexPath) as! <your Custom Cell>
cell.<your CustomCell label name>.textColor = UIColor.red
//OR
cell.<your Customcell label name>.backgroundColor = UIColor.green
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}
func tableView(tableView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath)
{
let cell = tableView.cellForRowAtIndexPath(indexPath) as! <your Custom Cell>
// change color back to whatever it was
cell.<your Customcell label name>.textColor = UIColor.black
//OR
cell.<your Customcell label name>.backgroundColor = UIColor.white
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}
Add tag to cell as indexPath.row
cell.tag = indexPath.row
Then
#objc func PostTap(_ sender: UIGestureRecognizer) {
let cell = self.tableVIew.cellForRow(at: sender.tag) as! HomeTVC
// Now you access your cell label here, and can do whatever you want
}
you can make it possible by using
tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath)
when user tap on a cell this method called
in this method do this
tableView.cellForRow(at: indexPath)
this will give you cell cast it as your cell class
and now u can do anything with your label in that cell
cell.label....
To change the color of clicked index label first you need to declare on varible to identify the clicked position
var selectedCellIndex = "" // initialize as empty string
In you cellForRowAt
func HomeProfilePlaceTVC(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, streamsModel : streamModel,HOMEPROFILE: HomeProfile, controller: UIViewController) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTVC", for: indexPath) as! HomeTVC
cell.post.text = streamsModel.Posts[indexPath.row]
cell.post.tag = indexPath.row
cell.post.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PostTap(_:)))
tapGesture.delegate = self as? UIGestureRecognizerDelegate
cell.post.addGestureRecognizer(tapGesture)
Post = streamsModel.Posts
if self.selectedCellIndex == "\(indexPath.row)" {
cell.post.text = UIColor.red
} else {
cell.post.text = UIColor.blue
}
return cell
}
In your Tap function
func PostTap(_ sender:UIGestureRecognizer){
let tapView = gesture.view!
let index = tapView.tag
self. selectedCellIndex = "\(index)"
self.YOUR_TABLE_NAME.reloadData()
}
Hope this will help you
Try Closure approach in Cell:
In Custom Table View cell:
class HomeTVC: UITableViewCell {
#IBOutlet weak var labelPost: UILabel!
var callBackOnLabelTap: (()->())?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(postTap(_:)))
tapGesture.numberOfTapsRequired = 1
tapGesture.delegate = self
self.labelPost.addGestureRecognizer(tapGesture)
}
#objc func postTap(_ sender: UIGestureRecognizer) {
self.callBackOnLabelTap?()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Then in cellForRowAt indexPath :
func HomeProfilePlaceTVC(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, streamsModel : streamModel,HOMEPROFILE: HomeProfile, controller: UIViewController) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTVC", for: indexPath) as! HomeTVC
cell.callBackOnLabelTap = {
cell.labelPost.backgroundColor = UIColor.black
}
return cell
}
For me, I wanted the color for the label to change when the container cell of a label is tapped.
You can select what color you want for the Label text, when tapped by selecting, Highlighted (in Attributes inspector) for Label. From drop down you can select the color you want to see when the cell was tapped.
Attributes Inspector: Highlighted Property for label

Break error while creating a calendar

Have break error Thread 1: EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode==0x0). No errors with build, just when run, have a break
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let calendars = self.calendars {
return calendars.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
//error happens here
if self.calendars != nil {
let calendarName = self.calendars?[(indexPath as NSIndexPath).row].title
cell.textLabel?.text = calendarName
} else {
cell.textLabel?.text = "Unknown Calendar Name"
}
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! UINavigationController
let addCalendarVC = destinationVC.viewControllers[0] as! AddCalendarViewController
addCalendarVC.delegate = self
}
func calendarDidAdd() {
self.loadCalendars()
self.refreshTableView()
}
}
tableView.dequeueReusableCell(withIdentifier: "Cell")!
You are unwrapping an optional value which might be nil in the first place. Cell might not have been created yet especially if you haven't registered the cell's class with that identifier so it'll crash first time table tries to populate the cell. You should first check if cell is nil:
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
}
...
The immediate red flag I see is here:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
When dequeueing reusable cells, I like to wrap them in guard statements, so my app doesn't crash. It also tells me a bit more information when something does go wrong:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else {
print("couldn't dequeue a reusable cell for identifier Cell in \(#function)")
return UITableViewCell()
}
This crash could be for a few reasons. You may have forgotten to register the reuse identifier, but if you're using storyboards this is handled for you. There may simply be a typo or you forgot to enter a reuse identifier for that cell.

Resources