简体   繁体   中英

How to edit and pass back cell data with delegate and protocol in swift

I have two VC, the first is a tableView, the second is the detailedView VC where you can add a new item to the tableView. I have implemented passing data forward with segues (from plus button in the first VC) and backwards with delegate and protocol when adding a new item to the tableView (triggered when tapping a save button on the second VC).

I added a segue from the prototype cell to the second VC (detailed view), I have also managed to test in the first VC which segue is triggered, ie: add new item or go to the detailedView of that item. the problem I'm facing, the save button in the second VC no longer works (and the cancel button also), I want to be able to edit the text fields in the second VC and hit the save button to save the edited item back in the first one. I found a way to implement it with unwind segues, however I would like to know how to do it with delegate ?

My first VC code:

class ThingsTableViewController: UITableViewController, CanReceive {

    var myThings = [Thing]()

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myThings.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

        cell.textLabel?.text = myThings[indexPath.row].name
        cell.detailTextLabel?.text = myThings[indexPath.row].type

        return cell
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if segue.identifier == "addNewThing" {

            let secondVC = segue.destination as! UINavigationController

            let ThingsViewController = secondVC.topViewController as! ThingsViewController

            ThingsViewController.delegate = self

        } else if segue.identifier == "showDetail" {

            guard let thingDetailViewController = segue.destination as? ThingsViewController else {fatalError("Unknown Destination")}

            guard let selectedCell = sender as? UITableViewCell else {
                fatalError("Unexpected sender: \(sender)")
            }

            guard let indexPath = tableView.indexPath(for: selectedCell) else {
                fatalError("The selected cell is not being displayed by the table")
            }

            let selectedThing = myThings[indexPath.row]
            thingDetailViewController.thing = selectedThing

        }
    }

    func dataReceived(data: Thing) {

        if let selectedIndexPath = tableView.indexPathForSelectedRow {

        myThings[selectedIndexPath.row] = data
        tableView.reloadRows(at: [selectedIndexPath], with: .none)

        } else {

        myThings.append(data)
        tableView.reloadData()



    }

}

the code in the second vc look like:

 protocol CanReceive {

func dataReceived(data: Thing)

    }

}

class ThingsViewController: UIViewController, UITextFieldDelegate {

    var delegate : CanReceive?
    var thing : Thing?

    @IBOutlet weak var thingNameTextField: UITextField!

    @IBOutlet weak var thingTypeTextfield: UITextField!

    @IBAction func saveThingButton(_ sender: UIBarButtonItem) {
        let newThing = Thing(name: thingNameTextField.text!, type: thingTypeTextfield.text!)

        delegate?.dataReceived(data: newThing)

        self.dismiss(animated: true, completion: nil)
        self.navigationController?.popViewController(animated: true)

    }

    @IBAction func cancelButton(_ sender: UIBarButtonItem) {
        self.dismiss(animated: true, completion: nil)
        self.navigationController?.popViewController(animated: true) 
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        thingNameTextField.delegate = self

        updateSaveButtonState()

        if let thing = thing {
            navigationItem.title = thing.name
            thingNameTextField.text = thing.name
            thingTypeTextfield.text = thing.type

        }
    }

    // MARK: UITextField Delegate

    // get triggered when the user hit the return key on the keyboard
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        thingNameTextField.resignFirstResponder()
        self.navigationItem.rightBarButtonItem?.isEnabled = true
        return true
    }

    //gives chance to read info in text field and do something with it
    func textFieldDidEndEditing(_ textField: UITextField) {
        updateSaveButtonState()
        navigationItem.title = thingNameTextField.text
    }

    func updateSaveButtonState() {

        let text = thingNameTextField.text
        self.navigationItem.rightBarButtonItem?.isEnabled = !text!.isEmpty
    }

}

In ThingsViewController class, please define delegate with weak var

weak var delegate: CanReceive?

One more issue is observed,

Looks like your instance name and class name are same, please update the instance name,

if segue.identifier == "addNewThing" {
    let secondVC = segue.destination as! UINavigationController
    let thingsVC = secondVC.topViewController as! ThingsViewController
    thingsVC.delegate = self 
} else if segue.identifier == "showDetail" { 
    guard let thingDetailViewController = segue.destination as? 
    ThingsViewController else {fatalError("Unknown Destination")}

        guard let selectedCell = sender as? UITableViewCell else {
            fatalError("Unexpected sender: \(sender)")
        }

        guard let indexPath = tableView.indexPath(for: selectedCell) else {
            fatalError("The selected cell is not being displayed by the table")
        }

        let selectedThing = myThings[indexPath.row]
        thingDetailViewController.thing = selectedThing
        thingDetailViewController.delegate = self
}

Your tableView.reloadData() should happen in main queue

func dataReceived(data: Thing) {
    myThings.append(data)
    DispatchQueue.main.async {
        tableView.reloadData()
    }
}

You're setting delegate for case that segue's identifier is addNewThing , but what about case that identifier is showDetail ?

Set delegate of segue's destination for case that segue's identifier is showDetail

if segue.identifier == "addNewThing" {
    ...
} else if segue.identifier == "showDetail" {
    ...
    thingDetailViewController.delegate = self
    ...
}

Then when you need to dismiss ViewController embed in navigation controller, just dismiss it and then dismiss navigation controller

Declare a protocol for receiving data.

protocol ViewControllerDelegate: class {
func didTapButton(with data: Int)
}

declare a delegate of protocol where you are sending the data

class SecondVC: UIViewController {

weak var delegate: ViewControllerDelegate?

@IBAction func buttonPressed(_ sender: UIButton) {
    delegate?.didTapButton(with: sender.tag)
}

}

confirm to the protocol where you want to receive the data and make the delegate to self.

class FirstVC : UIViewController,ViewControllerDelegate {


override func viewDidLoad() {
    super.viewDidLoad()

}

func gotoSecond() {
    let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "identifier") as! SecondVC
    vc.delegate = self
    self.navigationController?.pushViewController(vc, animated: true)
}

func didTapButton(with data: Int) {
    print(data)
}


}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM