Swift 3 - Rearrange and persist the cells in tableview - uitableview

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")
}
}

Related

How to change tableView numberOfRows and Cells based on which button is clicked in Swift iOS

I have two buttons in my user's profile page, one for the saved shop items and one for his reviews.
I want when the user clicks the saved button it would load his saved shop's items in the table view and when he clicks the reviews button it would load his reviews.
I'm struggling on how to figure out how to do this
Any help, please?
here is my code:
#IBOutlet weak var reviewsBtn: UIButton!
#IBOutlet weak var saveBtntab: UIButton!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(reviewsBtn.isSelected == true){
print("review selected")
return reviews.count
}
if(saveBtntab.isSelected == true){
print("saved selected")
return shops.count
}
return shops.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellFave", for: indexPath) as! FaveTableViewCell
let shops = self.shops[indexPath.row]
let reviews = self.reviews[indexPath.row]
// i want to do the same idea for the number of rows here.
}
#IBAction func reviewsTapped(_ sender: Any) {
reviewsBtn.isSelected = true
reviewsBtn.isEnabled = true
faveBtntab.isEnabled = false
faveBtntab.isSelected = false
}
#IBAction func savedTapped(_ sender: Any) {
faveBtntab.isSelected = true
faveBtntab.isEnabled = true
reviewsBtn.isEnabled = false
reviewsBtn.isSelected = false
}
First of all if there are only two states you can simplify numberOfRows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return reviewsBtn.isSelected ? reviews.count : shops.count
}
In cellForRow do the same thing, display the items depending on reviewsBtn.isSelected
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellFave", for: indexPath) as! FaveTableViewCell
if reviewsBtn.isSelected {
let reviews = self.reviews[indexPath.row]
// assign review values to the UI
} else {
let shops = self.shops[indexPath.row]
// assign shop values to the UI
}
}
And don't forget to call reloadData when the state has changed.
You can create two different dataSource instances for clarity and separation like following -
class ShopsDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
var shops: [Shop] = []
var onShopSelected: ((_ shop: Shop) -> Void)?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shops.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ShopTableViewCell", for: indexPath) as! ShopTableViewCell
let shop = self.shops[indexPath.row]
cell.populateDetails(shop: shop)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.onShopSelected?(shops[indexPath.row])
}
}
class ReviewsDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
var reviews: [Review] = []
var onReviewSelected: ((_ review: Review) -> Void)?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return reviews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewTableViewCell", for: indexPath) as! ReviewTableViewCell
let review = self.reviews[indexPath.row]
cell.populateDetails(review: review)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.onReviewSelected?(reviews[indexPath.row])
}
}
class ViewController: UIViewController {
let shopsDataSource = ShopsDataSource()
let reviewsDataSource = ReviewsDataSource()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(ShopTableViewCell.self, forCellReuseIdentifier: "ShopTableViewCell")
tableView.register(ReviewTableViewCell.self, forCellReuseIdentifier: "ReviewTableViewCell")
shopsDataSource.onShopSelected = { [weak self] (shop) in
self?.showDetailsScreen(shop: shop)
}
reviewsDataSource.onReviewSelected = { [weak self] (review) in
self?.showDetailsScreen(review: review)
}
}
#IBAction func shopsTapped(_ sender: Any) {
tableView.dataSource = shopsDataSource
tableView.delegate = shopsDataSource
tableView.reloadData()
}
#IBAction func addNewShop(_ sender: Any) {
/// ask user about shop details and add them here
shopsDataSource.shops.append(Shop())
tableView.reloadData()
}
func showDetailsScreen(shop: Shop) {
/// Go to shop details screen
}
#IBAction func reviewsTapped(_ sender: Any) {
tableView.dataSource = reviewsDataSource
tableView.delegate = reviewsDataSource
tableView.reloadData()
}
#IBAction func addNewReview(_ sender: Any) {
/// ask user about review details and add them here
reviewsDataSource.reviews.append(Review())
tableView.reloadData()
}
func showDetailsScreen(review: Review) {
/// Go to review details screen
}
}

Multiple selected rows to show multiple images in other view controller

Choosing a string in a UITableViewController, shows pic of the same name in another view controller, which works as it should.
But the problem is when I want to choose multiple strings to show multiple different pics.
I've been trying to add another IBOutlet, doubling stuff, but it was just showing me the same pic twice.
Any idea?
multiple selection = true
First VC segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "tablesegue" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let selectedRow = indexPath.row
let passingVal = segue.destination as! Tabulka_data
passingVal.selectedImageName = self.tableItems[selectedRow]
}
}
}
secondVC:
#IBOutlet weak var pic: UIImageView!
var selectedImageName:String = ""
override func viewWillAppear(_ animated: Bool) {
self.pic.image = UIImage(named: selectedImageName)
}
two choices
You can try this
let yourDataArray = ["name 1","name 2","name 3","name 4","name 5"]
var imageSelected = [String]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourDataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = yourTable.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = yourDataArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
imageSelected.append(yourDataArray[indexPath.row])
print(imageSelected)
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
for (index, element) in imageSelected.enumerated() {
if element == yourDataArray[indexPath.row] {
imageSelected.remove(at: index)
}
}
print(imageSelected)
}
You can use didSelectRowAt and didDeoRowAt to see which rows are selected, then just pass imageSelected to the second viewController

How to delete custom cell with timer in UITableView?

I'm developing an application that has a "Plus" button which can add stopwatch to a table view, every cell has its own timer, and can be played by itself.
When I'm trying to delete one cell like that, random issues are happening like:
Order of the stopwatches being changed
some stopwatches time is being zeroed .
If trying to add new stopwatch after, an old stopwatch with it's timer are back!
TableView
class StopWatchViewController: UIViewController {
#IBOutlet weak var stopWatchesTableView: UITableView!
var stopwatchesList: [String] = []
var stopwatchesNum : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
stopWatchesTableView.delegate = self
stopWatchesTableView.dataSource = self
NotificationCenter.default.addObserver(self,
selector: #selector(applicationDidEnterBackground(noti:)),
name: UIApplication.didEnterBackgroundNotification,
object: nil)
}
#objc func applicationDidEnterBackground(noti: Notification) {
// Save Date
let shared = UserDefaults.standard
shared.set(Date(), forKey: "SavedTime")
print(Date())
}
func refresh() {
stopWatchesTableView.reloadData()
}
#IBAction func AddStopWatch(_ sender: Any) {
stopwatchesNum += 1;
stopwatchesList.append(String(format: "Stopwatch %d", stopwatchesNum))
refresh()
}
}
extension StopWatchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stopwatchesList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let stopWatch = stopwatchesList[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "StopwatchCell") as! StopWatchCell
cell.initCell(title: stopWatch, index: indexPath.row)
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
stopwatchesList.remove(at: indexPath.row)
stopWatchesTableView.deleteRows(at: [indexPath], with: .automatic)
refresh()
}
}
}
What can cause such issues ?
Don't call refresh method after deleting the row.Hope this help.

Can't pass through data to second controller with segue

I can't get a simple segue to work when a cell in a tableview gets pressed. It does go to the next view after I tapped two different items. But I can't pass any values from the first controller to the second. If I set a value to the label in the second controller and load it in the viewDidLoad method it shows up.
I'm going crazy as I've been trying to get this work for ages....
My storyboard: https://snag.gy/DCw9MU.jpg
CategoryListViewController(1st controller):
import Foundation
import UIKit
class CategoryListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var categoryList = TestData.sharedInstance.categoryList
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "iEngineer"
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.tableView .reloadData()
tableView.dataSource = self
for category in categoryList{
print(category)
}
}
// MARK: - Segues
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showFormulaList" {
if let indexPath = tableView.indexPathForSelectedRow {
let category = self.categoryList[indexPath.row]
let formulaListViewController = (segue.destination as! UINavigationController).topViewController as! FormulaListViewController
formulaListViewController.text = category
formulaListViewController.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
formulaListViewController.navigationItem.leftItemsSupplementBackButton = true
}
}
}
// MARK: - Table View
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(categoryList.count)
return categoryList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "categoryCell", for: indexPath)
let object = categoryList[indexPath.row]
cell.textLabel!.text = object
return cell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showFormulaList", sender: self)
}
}
FormulaListViewController(2nd controller):
import Foundation
import UIKit
class FormulaListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var titleLabel: UILabel!
var formulaList = TestData.sharedInstance.formulaList
var fSwift: String!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "iEngineer"
print(fSwift)
titleLabel.text = fSwift
}
// MARK: - Table View
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(formulaList.count)
return formulaList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "formulaCell", for: indexPath)
let object = formulaList[indexPath.row]
print(object)
cell.textLabel!.text = object
return cell
}
}
Where is my mistake or what am I doing wrong?
I greatly appreciate any help
You need didSelectRowAt instead of didDeselectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showFormulaList", sender: self)
}
Also make sure segue source is the vc not the cell , and since you fire the segue in didDeselectRowAt this
if let indexPath = tableView.indexPathForSelectedRow
will be nil
You have used didDeselectRowAt:
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showFormulaList", sender: self)
}
You need to use didSelectRowAt:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showFormulaList", sender: self)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showFormulaList", sender: self)
}
You should use didSelect instead of didDeselectRowAt, also you should pass something better than self, because with self you are passing the entire CategoryListViewController
Try to pass the indexPath like this
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showFormulaList", sender: indexPath)
}
and change the function prepare in this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showFormulaList" {
if let indexPath = sender as? IndexPath {
let category = self.categoryList[indexPath.row]
let formulaListViewController = (segue.destination as! UINavigationController).topViewController as! FormulaListViewController
formulaListViewController.fuckSwift = category
formulaListViewController.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
formulaListViewController.navigationItem.leftItemsSupplementBackButton = true
}
}
}
If this doesn't help you try to debug your code and find where you lost your variable :)

Can't show the core data in tableview Swift

class showPageViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
#IBOutlet weak var tableView: UITableView!
var records : [Record] = []
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return records.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete{
let record = records[indexPath.row]
context.delete(record)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do{
records = try context.fetch(Record.fetchRequest())
} catch{
print("Failed")
}
}
}
override func viewWillAppear(_ animated: Bool) {
getData()
tableView.reloadData()
}
func getData(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do{
records = try context.fetch(Record.fetchRequest())
} catch{
print("123")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Hello everyone, I just tried to show the core data in table view, I already connect the dataSource and delegate to the ViewController, and I confirmed There are some data in core data, anyone can help me plz? thanks
Two big mistakes:
You cannot create a cell with the default initializer UITableViewCell() you have to dequeue it.
You have to get the item in the data source array for the index path and assign a value of a property to a label of the cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let record = records[indexPath.row]
cell.textLabel!.text = record.<nameOfProperty>
return cell
}
cell is the identifier specified in Interface Builder.
<nameOfProperty> is a property in your data model.

Resources