I created UITableViewController programmatically but methods for deleting rows editingStyle and didSelectRowAt not working. I cannot figure out why they don't work. I checked everything and tried by anywahy delegate methods not work.
My code:
import UIKit
class FPTasksViewController: UITableViewController, FPPopUpViewControllerDelegate {
let date = String(DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .long, timeStyle: .none))
private var tasks: [FPTask] = FPDefaults.sh.tasks {
didSet {
FPDefaults.sh.tasks = self.tasks
}
}
// MARK: - life cycle functions
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.separatorStyle = .none
let tapGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(didTapTableView))
tableView.addGestureRecognizer(tapGestureRecognizer)
}
// MARK: - actions
func FPPopUpViewControllerOkButtonTapped(_ controller: FPPopUpViewController, didFinishAdding newTask: FPTask) {
let newRowIndex = tasks.count
self.tasks.append(newTask)
let indexPath = IndexPath(row: newRowIndex, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
navigationController?.popViewController(animated: true)
}
#objc func addButtonTapped() {
let popUp = FPPopUpViewController()
popUp.delegate = self
self.view.addSubview(popUp)
self.view.backgroundColor = UIColor.systemGray3.withAlphaComponent(0.8)
}
#objc func didTapTableView(_ gestureRecognizer: UILongPressGestureRecognizer) {
self.tableView.backgroundColor = .black
}
// MARK: - table view
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
switch editingStyle {
case .delete:
self.tasks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
default:
break
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: FPTasksCell.reuseIdentifier,
for: indexPath) as? FPTasksCell ?? FPTasksCell()
let task = tasks[indexPath.row]
cell.setCellData(taskName: " \(task.taskTitle)", taskDescription: " from \(date.lowercased())")
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(1)
}
}
Why I cannot delete rows? And how to make didSelectRowAt method works also? What I did wrong?
set tableView datasource and delegate in viewDidLoad method.
self.tableView.delegate = self
self.tableView.dataSource = self
class FPTasksViewController: UITableViewController, FPPopUpViewControllerDelegate, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tableView.delegate = self
self.tableView.dataSource = self
}
}
Related
my cells are not appearing.
I did:
Checked if datasource and delegate were connected
Checked if my custom cells identifier name and class were correct
Things that I didn't:
I am struggling with auto layout, so I just decided not to do it.
My app is loading with the correct amount of cells, but the cells are not registered.
My code:
import UIKit
class WelcomeViewController: UITableViewController, NetworkManagerDelegate {
private var networkManager = NetworkManager()
private var infoForCells = [Result]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "ImageViewCell", bundle: nil), forCellReuseIdentifier: "imageCell")
networkManager.delegate = self
networkManager.fetchNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoForCells.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? ImageViewCell else{
return UITableViewCell(style: .default, reuseIdentifier: "cell")
}
let cellIndex = infoForCells[indexPath.row]
cell.titleForImage.text = cellIndex.alt_description
print(cell.titleForImage ?? "lol")
// if let image = cellIndex.urlToImage {
// cell.imageForArticle.load(url: image)
// }
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func didUpdateNews(root: Root) {
infoForCells = root.results
}
}
Reload the table
func didUpdateNews(root: Root) {
infoForCells = root.results
tableView.reloadData()
}
In addition to Sh_Khan answer you can also listen to updates of infoForCells property
private var infoForCells = [Result]() {
didSet {
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
}
I have a tableview which I can add items to it and it will save to core data, I can also delete these items and it all works fine
However now I want to rearrange the cells and persist the data as well
At the moment I can select the barbutton Edit and it will allow me to rearrange the cells but the moment i leave the viewcontroller it will reset to how it was
Can someone please help me?
class CustomWorkoutViewController: UIViewController {
#IBOutlet weak var newMusclesTableView: UITableView!
var workout:Workout?
override func viewDidLoad() {
super.viewDidLoad()
newMusclesTableView.delegate = self
let nib = UINib(nibName: "muscleListTableViewCell", bundle: nil)
newMusclesTableView.register(nib, forCellReuseIdentifier: "workCell")
}
override func viewDidAppear(_ animated: Bool) {
self.newMusclesTableView.reloadData()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addMuscles"{
guard let destination = segue.destination as? AddMusclesViewController else {
return
}
destination.workout = workout
}
else if segue.identifier == "addLogs"{
guard let destination = segue.destination as? WorkoutViewController,
let selectedRow = self.newMusclesTableView.indexPathForSelectedRow?.row else {
return
}
destination.muscleLog = workout?.muscleList?[selectedRow]
}
}
func btnAction(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: newMusclesTableView as UIView)
let indexPath: IndexPath! = newMusclesTableView.indexPathForRow(at: point)
let vc = viewMusclesViewController()
let viewTitle = workout?.muscleList?[indexPath.row]
vc.customInit(title: (viewTitle?.name)!)
vc.titleStr = viewTitle?.name
vc.gifStr = viewTitle?.muscleImage
navigationController?.pushViewController(vc, animated: true)
}
#IBAction func editAction(_ sender: UIBarButtonItem) {
self.newMusclesTableView.isEditing = !self.newMusclesTableView.isEditing
sender.title = (self.newMusclesTableView.isEditing) ? "Done" : "Edit"
}
func deleteMuscle(at indexPath: IndexPath){
guard let muscles = workout?.muscleList?[indexPath.row],
let managedContext = muscles.managedObjectContext else{
return
}
managedContext.delete(muscles)
do{
try managedContext.save()
newMusclesTableView.deleteRows(at: [indexPath], with: .automatic)
}catch{
print("Could not save")
newMusclesTableView.reloadRows(at: [indexPath], with: .automatic)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
And this is my tableview extension
extension CustomWorkoutViewController: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return workout?.muscleList?.count ?? 0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = newMusclesTableView.dequeueReusableCell(withIdentifier: "muscleCell", for: indexPath) as? muscleListTableViewCell
cell?.cellView.layer.cornerRadius = (cell?.cellView.frame.height)! / 2
if let muscles = workout?.muscleList?[indexPath.row]{
cell?.muscleTitle?.text = muscles.name
cell?.myBtn.addTarget(self, action: #selector(self.btnAction(_:)), for: .touchUpInside)
}
return cell!
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete{
deleteMuscle(at: indexPath)
}
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
//How to persist data here?
}
}
Your code decides which item to display for a row with this code:
if let muscles = workout?.muscleList?[indexPath.row]
The row order is going to be determined by the order in muscleList. The table view can rearrange cells when you use its edit mode, but it can't save that new order because it doesn't know how to change the order of muscleList. Your implementation of tableView(_:moveRowAt:to:) needs to change the order based on the table view update.
If muscleList is an ordered relationship, change the order. If it's not an ordered relationship then you'll need to add a property that you can use to sort the relationship-- even something as simple as a sortOrder property would do.
I managed to find a solution to my own question
I will post it here for future if anyone needed help
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let muscle = workout?.muscleList?[sourceIndexPath.row]
workout?.removeFromRawMuscles(at: sourceIndexPath.row)
workout?.insertIntoRawMuscles(muscle!, at: destinationIndexPath.row)
do{
try muscle?.managedObjectContext?.save()
}catch{
print("Rows could not be saved")
}
}
So I'm having this issue that when reloadData() is called after the initial API call, it calls willDisplayCell method which when the last cell is displayed will load more data (API returns 10 data at a time).
However in the view it can show only 4 - 5 cells as I set the row height to 175 points manually. Does anyone know why is this happening?
If this is how the tableView works what can I do to make my tableView to load only 10 initially?
Following is my code.
class MainViewController: UIViewController {
var tableView: UITableView!
var photoViewmodel: PhotoViewModel!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Home"
initTableView()
photoViewmodel = PhotoViewModel()
photoViewmodel.loadNewPhotos {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
func initTableView() {
tableView = UITableView.init(frame: self.view.frame, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 175
tableView.separatorStyle = .none
tableView.register(HomeTableViewCell.nib, forCellReuseIdentifier: HomeTableViewCell.identifier)
view.addSubview(tableView)
}
}
extension MainViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return photoViewmodel.photos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRowAt")
let cell = tableView.dequeueReusableCell(withIdentifier: HomeTableViewCell.identifier, for: indexPath) as! HomeTableViewCell
cell.tag = indexPath.row
cell.configureCell(url: photoViewmodel.photos[indexPath.row].regularImgUrl, cacheKey: indexPath.row)
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
print("willDisplay")
let lastCell = photoViewmodel.photos.count - 1
if indexPath.row == lastCell {
print("add 10 more photos")
photoViewmodel.loadNewPhotos(
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
}
}
Can anyone see why in the world didSelectRowAtIndexPath would not be called? I have triple checked by delegate both in the code and in storyboard.
class AddCard: UIViewController,UIPopoverPresentationControllerDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var cardView: UIView!
#IBOutlet weak var tableView: UITableView!
let tableItems = ["Background Color","Background Image","Font Style","Font Color"]
let cellID = "cell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setBackgroundColor (_ color: UIColor) {
cardView.backgroundColor = color
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath as IndexPath)
let row = indexPath.row
cell.textLabel?.text = tableItems[row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
print(indexPath.row)
let row = indexPath.row
switch(row){
case 0:
let popoverVC = storyboard?.instantiateViewController(withIdentifier: "colorPickerVC") as! ColorPickerViewController
popoverVC.modalPresentationStyle = .popover
popoverVC.preferredContentSize = CGSize(width: 284, height: 446)
if let popoverController = popoverVC.popoverPresentationController {
popoverController.sourceView = self.view
popoverController.sourceRect = CGRect(x: 0, y: 0, width: 85, height: 30)
popoverController.permittedArrowDirections = .any
popoverController.delegate = self
popoverVC.delegate = self
}
present(popoverVC, animated: true, completion: nil)
break
default: break
}
}
}
Swift 3 modified the signature of the method (a lot of methods too, new "rules"/style)
Replace:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) with
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Notice the _, the didSelectRowAt vs didSelectRowAtIndexPath, like the other ones you updated (which adapted also the same "style"), but not this one.
Remove the line and let XCode do the autocompletion. Else, you can just replace it with the one from the doc.
Ok I am brand new to this and am a bit overwhelmed going through many tutorials and articles. And spent a few hours sorting through similar issues with no luck in fixing my own. I have a "AddSiteVC" to allow the user to add or delete Items that are put into CoreData and then displayed in a TableView on my "MainVC". My problem is when I press save or delete and get dismissed back to my MainVC onBtnClick the TableView doesn't update until I leave the MainVC and then come back. I don't know what I'm doing wrong but can't seem to find anything that fixes this... I don't know where my problem is so I'll include most of my MainVC code for reference.
Any help would be greatly appreciated!
Thanks!
import UIKit
import CoreData
class SitesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableView: UITableView!
var controller: NSFetchedResultsController<Sites>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
attemptFetch()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SitesCell", for: indexPath) as! SitesCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return UITableViewCell()
}
func configureCell(cell: SitesCell, indexPath: NSIndexPath) {
let sites = controller.object(at: indexPath as IndexPath)
cell.configureCell(sites: sites)
cell.accessoryType = .detailButton
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AddSiteViewController" {
if let destination = segue.destination as? AddSiteViewController {
if let site = sender as? Sites {
destination.siteToEdit = site
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = controller.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
func numberOfSections(in tableView: UITableView) -> Int {
if let sections = controller.sections {
return sections.count
}
return 0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 75
}
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
if let objs = controller.fetchedObjects, objs.count > 0 {
let site = objs[indexPath.row]
performSegue(withIdentifier: "AddSiteViewController", sender: site)
}
}
func attemptFetch() {
let fetchRequest: NSFetchRequest<Sites> = Sites.fetchRequest()
let alphebaticalSort = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [alphebaticalSort]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
do {
try controller.performFetch()
} catch {
let error = error as NSError
print("\(error)")
}
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case.insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
case.delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
break
case.update:
if let indexPath = indexPath {
let cell = tableView.cellForRow(at: indexPath) as! SitesCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
}
break
case.move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
Please make following change in the code . Because viewDidLoad will be called when viewcontroller is loaded . But as per your requirement you adding something in Modal page. So you need move the code to viewWillAppear
import UIKit
import CoreData
class SitesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableView: UITableView!
var controller: NSFetchedResultsController<Sites>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
attemptFetch()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SitesCell", for: indexPath) as! SitesCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell }