Reputation: 227
I have two viewControllers one called programlist that displays the list of tiles and populates a a suitable view.
the second viewController inputs the data. Issues implementing the callback due to an error in the prepareForsegue
function. Getting the error "Instance member 'callback' cannot be used on type 'addWorkout'"
viewController 1 aka Programlist:
import UIKit
struct Item: Codable {
var title: String
var others: [String]
}
class ProgramList: UIViewController, UITableViewDataSource, UITableViewDelegate{
var Programs = [Item]()
@IBOutlet weak var programTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
load()
}
//saving current state of programs array
func save() {
guard let data = try? JSONEncoder().encode(Programs) else { return }
UserDefaults.standard.set(data, forKey: "notes")
}
//loading saved program array
func load() {
guard let loadedData = UserDefaults.standard.data(forKey: "notes") else { return }
do {
Programs = try JSONDecoder().decode([Item].self, from: loadedData)
programTableView.reloadData()
} catch { print(error) }
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Programs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.programTitle.text = Programs[indexPath.row].title
return cell
}
//Removing Item by swipping left & saving this newly established array
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
Programs.remove(at: indexPath.row)
programTableView.reloadData()
save()
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddPage"{
workoutController.callback = { [weak self] string in
let entered = Item(title: string, others: ["hi"])
self?.programs.append(entered)
let indexPath = IndexPath(row: self?.programs.count - 1, section: 0)
self?.tableView.insertRows(at: [indexPath], with: .automatic)
self?.save()
}
}
}
}
}
}
viewController 2 aka addWorkout:
import UIKit
class addWorkout: UIViewController {
@IBOutlet weak var workoutTitle: UITextField!
var callback : ((String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func saveWorkoutTitle(_ sender: Any) {
if !workoutTitle.text!.isEmpty {
callback?(workoutTitle.text!)
}
}
}
Upvotes: 3
Views: 945
Reputation: 285069
The main mistake is you are trying to save an array of Item
– which is not supported anyway – to UserDefaults
and read an array of String
. That's a clear type mismatch.
To be able to save an array of a custom struct to UserDefaults
adopt Codable
to save the struct as JSON.
struct Item : Codable {
var title: String
var others: [String]
}
Further it's a very bad practice to declare a data source array outside of any class.
This is the ProgramList
class with adjusted load
and save
methods and the data source array inside the class. The method viewDidAppear
is not needed.
class ProgramList: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var programTableView: UITableView!
var programs = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
load()
}
//saving current state of programs array
func save() {
guard let data = try? JSONEncoder().encode(programs) else { return }
UserDefaults.standard.set(data, forKey: "notes")
}
//loading saved program array
func load() {
guard let loadedData = UserDefaults.standard.data(forKey: "notes") else { return }
do {
programs = try JSONDecoder().decode([Item].self, from: loadedData)
programTableView.reloadData()
} catch { print(error) }
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return programs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.programTitle.text = programs[indexPath.row].title
return cell
}
//Removing Item by swipping left & saving this newly established array
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
programs.remove(at: indexPath.row)
programTableView.deleteRows(at: [indexPath], with: .automatic)
save()
}
}
}
To share data between controllers use a closure as callback and pass the string
class AddWorkout: UIViewController {
@IBOutlet weak var workoutTitle: UITextField!
var callback : ((String) -> Void)?
@IBAction func saveWorkoutTitle(_ sender: Any) {
if !workoutTitle.text!.isEmpty {
callback?(workoutTitle.text!)
}
}
}
Back in ProgramList
controller assign a closure to the callback
property in prepareForSegue
(or right before presenting the controller)
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddPage" {
let workoutController = segue.destination as! AddWorkout
workoutController.callback = { string in
let entered = Item(title: string, others: ["hi"])
self.programs.append(entered)
let indexPath = IndexPath(row: self.programs.count - 1, section: 0)
self.tableView.insertRows(at: [indexPath], with: .automatic)
self.save()
}
}
}
Upvotes: 3