2

I have a table view (controller: MetricsViewController) which gets updated from a CoreData database. I have used prototype cells (MetricsViewCell) which I have customized for my needs. It contains a segmented control, a UIView (metricsChart, which is used to display a chart - animatedCircle), and some UILabels.

MetricsViewCell:

class MetricsViewCell: UITableViewCell {
    
    var delegate: SelectSegmentedControl?
    var animatedCircle: AnimatedCircle?
    
    @IBOutlet weak var percentageCorrect: UILabel!
    @IBOutlet weak var totalPlay: UILabel!
    
    @IBOutlet weak var metricsChart: UIView! {
        didSet {
            animatedCircle = AnimatedCircle(frame: metricsChart.bounds)
        }
    }
    @IBOutlet weak var recommendationLabel: UILabel!
    
    @IBOutlet weak var objectType: UISegmentedControl!
    
    @IBAction func displayObjectType(_ sender: UISegmentedControl) {
        delegate?.tapped(cell: self)
        
    }
}

protocol SelectSegmentedControl {
    func tapped(cell: MetricsViewCell)
}

MetricsViewController:

class MetricsViewController: FetchedResultsTableViewController, SelectSegmentedControl {

func tapped(cell: MetricsViewCell) {
    if let indexPath = tableView.indexPath(for: cell) {
        tableView.reloadRows(at: [indexPath], with: .none)
    }
}

var container: NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer { didSet { updateUI() } }

private var fetchedResultsController: NSFetchedResultsController<Object>?

private func updateUI() {
    if let context = container?.viewContext {
        let request: NSFetchRequest<Object> = Object.fetchRequest()
        request.sortDescriptors = []
        fetchedResultsController = NSFetchedResultsController<Object>(
            fetchRequest: request,
            managedObjectContext: context,
            sectionNameKeyPath: "game.gameIndex",
            cacheName: nil)
        try? fetchedResultsController?.performFetch()
        tableView.reloadData()
    }
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Object Cell", for: indexPath)
    if let object = fetchedResultsController?.object(at: indexPath) {
        
        if let objectCell = cell as? MetricsViewCell {
            objectCell.delegate = self
            
            let request: NSFetchRequest<Object> = Object.fetchRequest()
            ...
            ...

            }
        }
    }
    return cell
}

When a user selects one of the segments in a certain section's segmented control, MetricsViewController should reload the data in that particular row. (There are two sections with one row each). Hence, I've defined a protocol in MetricsViewCell to inform inform my controller on user action.

Data is being updated using FetchedResultsTableViewController - which basically acts as a delegate between CoreData and TableView. Everything is fine with that, meaning I am getting the correct data into my TableView.

There are two issues:

  1. I have to tap segmented control's segment twice to reload the data in the row where segmented control was tapped.

  2. The table scrolls back up and then down every time a segment from segmented control is selected.

Help would be very much appreciated. I've depended on this community for a lot of issues I've faced during the development and am thankful already :)

For example, in Animal Recognition section, I have to hit "Intermediate" two times for its row to be reloaded (If you look closely, the first time I hit Intermediate, it gets selected for a fraction of second, then it goes back to "Basic" or whatever segment was selected first. Second time when I hit intermediate, it goes to Intermediate). Plus, the table scroll up and down, which I don't want.

enter image description here

Edit: Added more context around my usage of CoreData and persistent container.

Community
  • 1
  • 1
Manganese
  • 650
  • 5
  • 26
  • 2. To avoid scrolling - Try Reload rows without animation. Presently you are specifying automatic animation. – user1046037 Feb 06 '18 at 06:24
  • I. How do you determine which segment control is tapped on ?. Each cell has has its own segment control – user1046037 Feb 06 '18 at 06:27
  • Doesn't work - with .none animation unfortunately. It's the default animation. – Manganese Feb 06 '18 at 06:30
  • Are you scrolling any where in your code ? – user1046037 Feb 06 '18 at 06:30
  • Through the protocol, I am sending the cells over, since I need the indexPath. Then I am choosing the first index path (function tapped) which is returned, that gives me which one the user has tapped. – Manganese Feb 06 '18 at 06:32
  • @user1046037 Not in my code, no. But my tableView is scrolling enabled as default. – Manganese Feb 06 '18 at 06:33
  • 1
    In your function `tapped(cell:)` then way you create `indexPath` seems to be wrong. Couple of mistakes here. You are hard coding row as zero. – user1046037 Feb 06 '18 at 06:37
  • @user1046037 Yeah thanks for that, I did that. Great suggestion, however it doesn't resolve the two issues which I have. – Manganese Feb 06 '18 at 06:41
  • You have convert the frame rect to the view’s frame text. Refer - https://stackoverflow.com/questions/36763360/how-to-get-center-point-of-cell?rq=1 – user1046037 Feb 06 '18 at 06:41
  • @user1046037 tableView.indexPathForRow(at: cell.center) was actually not an appropriate method to use here, hence replaced it with simply, tableView.indexPath(for: ). Your suggestion wouldn't be relevant in this context I think. Tx nevertheless. – Manganese Feb 06 '18 at 06:54

2 Answers2

2

Instead of using indexPathForRow(at: <#T##CGPoint#>) function to get the indexPath object of cell you can directly use indexPath(for: <#T##UITableViewCell#>) as you are receiving the cell object to func tapped(cell: MetricsViewCell) {} and try to update your data on the UI always in main thready as below.

func tapped(cell: MetricsViewCell) {
    if let lIndexPath = table.indexPath(for: <#T##UITableViewCell#>){
        DispatchQueue.main.async(execute: {
            table.reloadRows(at: lIndexPath, with: .none)
        })
    }
}
Jayachandra A
  • 1,335
  • 1
  • 10
  • 21
  • I am using viewContext of my app delegate's container, to update data. viewContext runs on main thread, hence that's not a problem. Still I tried explicit main thread, doesn't work :( – Manganese Feb 06 '18 at 06:45
  • 1
    @Manganese what about to get the indexpath object of a cell? Have tried it? – Jayachandra A Feb 06 '18 at 06:47
  • Yep, that was the same suggestion as by @user1046037 : func tapped(cell: MetricsViewCell) { if let indexPath = tableView.indexPath(for: cell) { DispatchQueue.main.async { self.tableView.reloadRows(at: [indexPath], with: .none) } } } – Manganese Feb 06 '18 at 06:48
1

Your UISegmentedControl are reusing [Default behaviour of UITableView].

To avoid that, keep dictionary for getting and storing values.

Another thing, try outlet connection as Action for UISegmentedControl in UIViewController itself, instead of your UITableViewCell

The below code will not reload your tableview when you tap UISegmentedControl . You can avoid, delegates call too.

Below codes are basic demo for UISegmentedControl. Do customise as per your need.

var segmentDict = [Int : Int]()


override func viewDidLoad() {
    super.viewDidLoad()

    for i in 0...29 // number of rows count
    {
        segmentDict[i] = 0 //DEFAULT SELECTED SEGMENTS
    }

}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SOTableViewCell

    cell.mySegment.selectedSegmentIndex = segmentDict[indexPath.row]!

    cell.selectionStyle = .none
    return cell
}

@IBAction func mySegmentAcn(_ sender: UISegmentedControl) {

    let cellPosition = sender.convert(CGPoint.zero, to: tblVw)
    let indPath = tblVw.indexPathForRow(at: cellPosition)

    segmentDict[(indPath?.row)!] = sender.selectedSegmentIndex


    print("Sender.tag     ", indPath)

}
McDonal_11
  • 3,935
  • 6
  • 24
  • 55