Good time of day, in my app i have a tableview and custom cell, in cell there are labels, button and progressBar, so that when i tap button download proceeds and progressBar shows progress, but when i scroll down i realise that there are other cells are selected and shows progress and when i scroll up again progress of my selected cell stops. Could you help, any feedbacks appreciated )
That's my TableViewController :
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return titles.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
cell.pesnya.text = titles[indexPath.row]
cell.pevets.text = artists[indexPath.row]
cell.url = urls[indexPath.row]
return (cell)
}
#IBAction func buttonPressed(_ sender: AnyObject) {
(sender as! UIButton).isSelected = !(sender as! UIButton).isSelected
if (sender as! UIButton).isSelected {
if let indexPath = tableView.indexPath(for: sender.superview!?.superview as! UITableViewCell) {
DownloadManager.shared.download(url: urls[indexPath.row], title: titles[indexPath.row])
}
} else {
// (sender as! UIButton).setTitle("Удалить", for: UIControlState.normal)
if let indexPath = tableView.indexPath(for: sender.superview!?.superview as! UITableViewCell) {
let name = "\(titles[indexPath.row]).mp3"
let name2 = name.replacingOccurrences(of: " ", with: "")
let filePathURL = URL(string:"string")
do {
try FileManager.default.removeItem(at: filePathURL!)
} catch {
print("Could not delete file: \(error)")
}
}
}
}
This is because table view cell will be reused when you scroll. Set views to initial status in cell's prepareForReuse. Like this:
class ACell: UITableViewCell {
override func prepareForReuse() {
view.hidden = true
}
}
try this :-
override func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int
{
return titles.count
}
override func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for:
indexPath) as! ViewControllerTableViewCell
cell.pesnya.text = titles[indexPath.row]
cell.pevets.text = artists[indexPath.row]
cell.url = urls[indexPath.row]
// Create IBOutlet for button
cell.btnPressed.tag = indexPath.row
cell.btnPressed.addTarget(self, action: #selector(self.buttonPressed(sender:)), for: .touchUpInside)
if cell.btnPressed.isSelected {
DownloadManager.shared.download(url: urls[indexPath.row], title: titles[indexPath.row])
}else {
let name = "\(titles[indexPath.row]).mp3"
let name2 = name.replacingOccurrences(of: " ", with: "")
let filePathURL = URL(string:"string")
do {
try FileManager.default.removeItem(at: filePathURL!)
} catch {
print("Could not delete file: \(error)")
}
}
return (cell)
}
#IBAction func buttonPressed(_ sender: UIButton) {
let indexPath = IndexPath(row: sender.tag, section: 0)
if let cell = tableView.cellForRow(at: indexPath) as?
ViewControllerTableViewCell {
cell.btnPressed.isSelected = !cell.btnPressed.isSelected
tableview.reloadData()
}
}
Related
I'm trying to make a filter. I got a tableview. When I click any section, it expands and collapses.
My problem is that when I open and close other sections after clicking on the checkboxes, unselected checkboxes in other sections appear as selected and selected ones are unselected. What should I do? Can you show me some code? Thanks!
https://ibb.co/0htP7Hz // Filter image
var hiddenSections = Set<Int>()
var filtersArray = Set<String>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "FilterCell", for: indexPath) as? FilterTableViewCell else
{
fatalError("Product Group Cell not found")
}
guard let item = self.filterElementListVM.itemfilterviewmodelAtIndex(indexPath) else {
return UITableViewCell()
}
cell.setupCell(title: item.definition ?? "", buttonTag: item.id ?? 0, filterArray: self.filtersArray)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? FilterTableViewCell {
let selectedFilterItem = self.filterElementListVM.itemfilterviewmodelAtIndex(indexPath)
if cell.buttonCheck.isSelected {
self.filtersArray.remove(String(selectedFilterItem?.definition ?? ""))
} else {
self.filtersArray.insert(String(selectedFilterItem?.definition ?? ""))
}
cell.buttonCheckTap()
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let viewHeader = UIView.init(frame: CGRect.init(x: 0.0, y: 0.0, width: tableView.frame.size.width, height: 67.0))
viewHeader.backgroundColor = .white
let filterVM : FilterViewModel = self.filterElementListVM.filterViewModelAtIndex(section)
let viewFilterHeader : ViewFilter = ViewFilter.init(title: filterVM.definition,
rightImage: UIImage.init(named: "arrow_down")!, isPropertiesChanged: false, isArrowHidden: false)
viewFilterHeader.tag = section
let tap = UITapGestureRecognizer(target: self, action: #selector(hideSection(_:)))
viewFilterHeader.addGestureRecognizer(tap)
viewHeader.addSubview(viewFilterHeader)
viewFilterHeader.snp.makeConstraints { (make) in
make.top.equalTo(7.0)
make.bottom.equalTo(0.0)
make.leading.trailing.equalTo(0)
}
return viewHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 67.0
}
#objc private func hideSection(_ sender: UITapGestureRecognizer? = nil) {
guard let section = sender?.view?.tag else { return }
func indexPathsForSection() -> [IndexPath] {
var indexPaths = [IndexPath]()
for row in 0..<self.filterElementListVM.numberOfRowsInSection(section) {
indexPaths.append(IndexPath(row: row,
section: section))
}
return indexPaths
}
if self.hiddenSections.contains(section) {
self.hiddenSections.remove(section)
self.tableviewFilter.insertRows(at: indexPathsForSection(),
with: .fade)
} else {
self.hiddenSections.insert(section)
self.tableviewFilter.deleteRows(at: indexPathsForSection(),
with: .fade)
}
}
Looks like your configuration works wrong and when you toggle your cell old data applied to your cell. So you need to clear all your data in prepareFroReuse() method inside your UIColelctionViewCell class
More information: https://developer.apple.com/documentation/uikit/uitableviewcell/1623223-prepareforreuse
I am working on a location-based reminder app. I show all reminders that user created on a table view. I have also UISwitch on every cell. I want that UISwitch disables/enables reminders individually, not all notifications. I couldn't figure it out.
extension MainViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! itemTableViewCell
let item = self.items![indexPath.row]
cell.itemTitle?.text = item.itemName
cell.itemSubTitle?.text = item.itemDescription
//switch
let swicthView = UISwitch(frame: .zero)
swicthView.onTintColor = UIColor (named: "DingerBlue")
swicthView.setOn(true, animated: true)
swicthView.tag = indexPath.row
swicthView.addTarget(self, action: #selector(self.SwitchBtn(_:)), for: .valueChanged)
cell.accessoryView = swicthView
let itemToRemove = self.items![indexPath.row]
let notifToRemove: String = itemToRemove.notifID!
return cell
}
#objc func switchDidChanged(_ sender: UISwitch){
print("Switch value is \(sender.isOn)")
if(sender.isOn){
print("on")
UIApplication.shared.registerForRemoteNotifications()
}
else{
print("Off")
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [notifToRemove])
}
}
}
I believe your code is not working because cells are reused and actions on specific cells should be handled from the cell class rather than from ViewController. Move this code into the UITableViewCell code.
let swicthView = UISwitch(frame: .zero)
swicthView.onTintColor = UIColor (named: "DingerBlue")
swicthView.setOn(true, animated: true)
swicthView.tag = switchViewTag!
swicthView.addTarget(self, action: #selector(self.SwitchBtn(_:)), for: .valueChanged)
#objc func switchDidChanged(_ sender: UISwitch){
print("Switch value is \(sender.isOn)")
if(sender.isOn){
print("on")
UIApplication.shared.registerForRemoteNotifications()
}
else{
print("Off")
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [notifToRemove])
}
}
and add a new property in the UITableViewCell
weak var switchViewTag: Int?
Modify your cellForRowAt delegate method to
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! itemTableViewCell
let item = self.items![indexPath.row]
cell.itemTitle?.text = item.itemName
cell.itemSubTitle?.text = item.itemDescription
cell.switchViewTag = indexPath.row
return cell
}
I want to delete tableview cell by clicking a button present in the same cell. But I am unable to access the cell in the button action function.
Please help me to Access this cell. My code is -
class MatchesViewController: UIViewController{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MatchingUsersTVCell") as? MatchingUsersTVCell else{
return UITableViewCell()
}
let likeUid = userIdArray[indexPath.row]
cell.heartBtn.tag = indexPath.row
cell.heartBtn.addTarget(self, action: #selector(userLikeButtonWasTappaed(sender:)), for: .touchUpInside)
}
#objc func userLikeButtonWasTappaed(sender: UIButton){
if let cell = sender.superview as? MatchingUsersTVCell{
CellAnimator.animate(cell: cell)
}
let tag = sender.tag
let userid = userIdArray[tag]
}
}
Try this code:
#objc func userLikeButtonWasTappaed(sender: UIButton){
guard let indexPath = tableView.indexPathForRow(at: sender.convert(sender.frame.origin, to: tableView)) else {
return
}
let cell = tableView.cellForRow(at: indexPath) as? MatchingUsersTVCell
}
And in your cellForRowAt function add the following code:
cell.yourBtn.tag = indexPath.row
cell.yourBtn.addTarget(self, action: #selector(userLikeButtonWasTappaed(sender:)), for: .touchUpInside)
I'd stay away from using tags, and instead implement protocol/delegate.
Using indexPath allows use of multiple sections, etc...
1) Create a protocol:
protocol MatchingUsersTVCellDelegate : class {
func didTapLikeButton(_ indexPath: IndexPath)
func didTapOtherButton(_ indexPath: IndexPath)
}
2) Create/Update your cell:
class MatchingUsersTVCell : UITableViewCell {
weak var delegate: MatchingUsersTVCellDelegate?
var indexPath: IndexPath!
// add target to your like button
func didTapLIkeButton(_ sender: UIButton) {
self.delegate?.didTapLikeButton(indexPath)
}
func didTapOtherButton() {
self.delegate?.didTapOtherButton(indexPath)
}
}
3) make sure your viewController conforms to the new delegate:
extension YourViewController: MatchingUsersTVCellDelegate {
func didTapLikeButton(_ indexPath: IndexPath) {
//Do something with the indexPath or indexPath.row
dataSource.remove(at: indexPath.row)
}
func didTapOtherButton(_ indexPath: IndexPath) {
//Do something else with the indexPath or indexPath.row
}
}
4) Set delegate and indexPath
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell...
cell.delegate = self
cell.indexPath = indexPath
return cell
}
Within MatchingUsersTVCell, add two properties, one named parentVC of type UIViewController and one named index of type Int:
class MatchingUsersTVCell: UITableViewCell {
var parentVC: UIViewController!
var index: Int!
...
}
Then, when creating each cell, set these two values appropriately:
class MatchesViewController: UIViewController, UITableViewDelegate, UITableViewDatasource {
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MatchingUsersTVCell") as? MatchingUsersTVCell else {
return UITableViewCell()
}
cell.parentVC = self
cell.index = index
...
return cell
}
}
Now, you simply update your parentVC's tableView's data source and reload its data whenever the button is tapped:
class MatchingUsersTVCell: UITableViewCell {
...
#objc func userLikeButtonWasTappaed(sender: UIButton){
parentVC.userIdArray.remove(at: index)
parentVC.tableView.reloadData()
}
}
you can get it like this in your selector method
#objc func userLikeButtonWasTappaed(button:UIButton){
guard let indexPath = myTableView.indexPathForRow(at: button.convertPoint(button.frame.origin, toView: myTableView)) else {
print("Error: indexPath)")
return
}
print("indexPath.row: \(indexPath.row)")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arraylist.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = PopupCellTableViewCell()
cell = (tableView.dequeueReusableCell(withIdentifier: "advnccell", for: indexPath) as? PopupCellTableViewCell)!
cell.bgView = Utilities().viewborder(vw: cell.bgView )
let dict = arraylist[indexPath.row] as! NSDictionary
let cd = dict.value(forKey: "Code") as? String ?? ""
let desc = dict.value(forKey: "Description") as? String ?? ""
cell.tittleLbl.text = cd + " - " + desc
if selectedRows.contains(indexPath)
{
cell.checkBtn.setImage(UIImage(named:"check"), for: .normal)
}
else
{
cell.checkBtn.setImage(UIImage(named:"uncheck"), for: .normal)
}
if (cell.checkBtn.currentImage?.isEqual(UIImage(named: "check")))!
{
UserDefaults.standard.set(cell.tittleLbl.text, forKey: "tittle")
}
cell.btnAction.tag = indexPath.row
cell.btnAction.addTarget(self, action: #selector(checkBoxSelection(_:)), for: .touchUpInside)
return cell
}
// checkbox selection
#objc func checkBoxSelection(_ sender:UIButton)
{
let selectedIndexPath = IndexPath(row: sender.tag, section: 0)
if self.selectedRows.contains(selectedIndexPath)
{
self.selectedRows.remove(at: self.selectedRows.index(of: selectedIndexPath)!)
}
else
{
self.selectedRows.append(selectedIndexPath)
}
self.tableVw.reloadData()
}
At present i am getting only one value, how can i get if i selected multiple check buttons
Fix this portion of your code:
if selectedRows.contains(indexPath)
{
cell.checkBtn.setImage(UIImage(named:"check"), for: .normal)
UserDefaults.standard.set(cell.tittleLbl.text, forKey: "tittle")
}
else
{
cell.checkBtn.setImage(UIImage(named:"uncheck"), for: .normal)
}
I would like that users can choose maximum one voice. And that the checkmark jumps to where you tapt and deselect the other.
It looks like very simple, but I don't see the solution. And I can't find the answer on the internet.
Please can anybody help me?
Thanks advance!
import UIKit
import AVFoundation
class voicesTableViewController: UITableViewController {
fileprivate let synthesizer = AVSpeechSynthesizer()
fileprivate var speechVoices = AVSpeechSynthesisVoice.speechVoices()
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
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
return speechVoices.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
//Name
let voice = speechVoices[indexPath.row]
let voiceLang = voice.language as? String
let theVoice = UserDefaults.standard.object(forKey:"voice") as? String
cell.textLabel?.text = voice.name
// Language
if let language = countryName(countryCode: voice.language) {
cell.detailTextLabel?.text = "\(language)"
}
else {
cell.detailTextLabel?.text = ""
}
cell.detailTextLabel?.textColor = UIColor.gray
// Checkmark
if (theVoice != nil) {
if(theVoice == voiceLang) {
cell.accessoryType = UITableViewCellAccessoryType.checkmark
}
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let voice = speechVoices[indexPath.row]
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCellAccessoryType.checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
}
else
{
//if ((tableView.indexPathsForSelectedRows?.count)! > 1) {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
//}
}
UserDefaults.standard.set(voice.language, forKey:"voice")
UserDefaults.standard.synchronize()
tableView.deselectRow(at: indexPath, animated: true)
}
func countryName(countryCode: String) -> String? {
let preferredLanguage = NSLocale.preferredLanguages[0] as String
let current = Locale(identifier: preferredLanguage)
return current.localizedString(forLanguageCode: countryCode) ?? nil
//return current.localizedString(forIdentifier: indentifier) ? nil
}
}
Simple change of function cellForRow:atIndexPathshould work:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
//Name
let voice = speechVoices[indexPath.row]
let voiceLang = voice.language as? String
let theVoice = UserDefaults.standard.object(forKey:"voice") as? String
cell.textLabel?.text = voice.name
// Language
if let language = countryName(countryCode: voice.language) {
cell.detailTextLabel?.text = "\(language)"
}
else {
cell.detailTextLabel?.text = ""
}
cell.detailTextLabel?.textColor = UIColor.gray
// Checkmark
if(theVoice != nil && theVoice == voiceLang) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
UPD#1
But you can use better solution:
1) Add property fileprivate var selectedIndexPath: IndexPath?
2) Change function func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)to next one:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
//Name
let voice = speechVoices[indexPath.row]
let voiceLang = voice.language as? String
let theVoice = UserDefaults.standard.object(forKey:"voice") as? String
cell.textLabel?.text = voice.name
// Language
if let language = countryName(countryCode: voice.language) {
cell.detailTextLabel?.text = "\(language)"
}
else {
cell.detailTextLabel?.text = ""
}
cell.detailTextLabel?.textColor = UIColor.gray
// Checkmark
cell.accessoryType = self.selectedIndexPath == indexPath ? .checkmark : .none
return cell
}
3) And then in func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) you can do next:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let voice = speechVoices[indexPath.row]
self.selectedIndexPath = indexPath
UserDefaults.standard.set(voice.language, forKey:"voice")
UserDefaults.standard.synchronize()
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadRows(at: [indexPath], with: .automatic)
}