如何在 swift 中使用委托和协议编辑和传回单元格数据
How to edit and pass back cell data with delegate and protocol in swift
我有两个 VC,第一个是 tableView,第二个是 detailedView VC,您可以在其中向 tableView 添加新项目。
我已经实现了使用 segues 向前传递数据(从第一个 VC 中的加号按钮)并在向 tableView 添加新项目时使用委托和协议向后传递数据(在第二个 VC 上点击保存按钮时触发) ).
我从原型单元格添加了一个 segue 到第二个 VC(详细视图),我还设法在第一个 VC 中测试了哪个 segue 被触发,即:添加新项目或转到该项目的详细视图。我面临的问题是,第二个 VC 中的保存按钮不再起作用(取消按钮也一样),我希望能够编辑第二个 VC 中的文本字段并点击保存按钮将编辑的项目保存回第一个。
我找到了一种使用 unwind segues 来实现它的方法,但是我想知道如何使用 delegate 来实现它?
我的第一个VC代码:
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()
}
}
第二个 vc 中的代码如下所示:
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
}
}
在ThingsViewController
class中,请用weak var
定义delegate
weak var delegate: CanReceive?
又发现一个问题,
看起来您的实例名称和 class 名称相同,请更新实例名称,
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
}
你的 tableView.reloadData()
应该会在 main queue
func dataReceived(data: Thing) {
myThings.append(data)
DispatchQueue.main.async {
tableView.reloadData()
}
}
声明接收数据的协议。
protocol ViewControllerDelegate: class {
func didTapButton(with data: Int)
}
在您要发送数据的地方声明一个协议委托
class SecondVC: UIViewController {
weak var delegate: ViewControllerDelegate?
@IBAction func buttonPressed(_ sender: UIButton) {
delegate?.didTapButton(with: sender.tag)
}
}
确认要接收数据的协议,并让委托给自己。
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)
}
}
您正在为 segue 的标识符为 addNewThing
的情况设置 delegate
,但是标识符为 showDetail
的情况呢?
如果 segue 的标识符是 showDetail
,则设置 segue 的目的地 delegate
if segue.identifier == "addNewThing" {
...
} else if segue.identifier == "showDetail" {
...
thingDetailViewController.delegate = self
...
}
然后当你需要关闭 ViewController embed in navigation controller 时,只需关闭它然后关闭导航控制器
我有两个 VC,第一个是 tableView,第二个是 detailedView VC,您可以在其中向 tableView 添加新项目。 我已经实现了使用 segues 向前传递数据(从第一个 VC 中的加号按钮)并在向 tableView 添加新项目时使用委托和协议向后传递数据(在第二个 VC 上点击保存按钮时触发) ).
我从原型单元格添加了一个 segue 到第二个 VC(详细视图),我还设法在第一个 VC 中测试了哪个 segue 被触发,即:添加新项目或转到该项目的详细视图。我面临的问题是,第二个 VC 中的保存按钮不再起作用(取消按钮也一样),我希望能够编辑第二个 VC 中的文本字段并点击保存按钮将编辑的项目保存回第一个。 我找到了一种使用 unwind segues 来实现它的方法,但是我想知道如何使用 delegate 来实现它?
我的第一个VC代码:
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()
}
}
第二个 vc 中的代码如下所示:
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
}
}
在ThingsViewController
class中,请用weak var
weak var delegate: CanReceive?
又发现一个问题,
看起来您的实例名称和 class 名称相同,请更新实例名称,
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
}
你的 tableView.reloadData()
应该会在 main queue
func dataReceived(data: Thing) {
myThings.append(data)
DispatchQueue.main.async {
tableView.reloadData()
}
}
声明接收数据的协议。
protocol ViewControllerDelegate: class {
func didTapButton(with data: Int)
}
在您要发送数据的地方声明一个协议委托
class SecondVC: UIViewController {
weak var delegate: ViewControllerDelegate?
@IBAction func buttonPressed(_ sender: UIButton) {
delegate?.didTapButton(with: sender.tag)
}
}
确认要接收数据的协议,并让委托给自己。
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)
}
}
您正在为 segue 的标识符为 addNewThing
的情况设置 delegate
,但是标识符为 showDetail
的情况呢?
如果 segue 的标识符是 showDetail
delegate
if segue.identifier == "addNewThing" {
...
} else if segue.identifier == "showDetail" {
...
thingDetailViewController.delegate = self
...
}
然后当你需要关闭 ViewController embed in navigation controller 时,只需关闭它然后关闭导航控制器