仍然是一个Swift noob,我一直在寻找一种正确的方法/最佳实践来管理UITableView
(使用自定义UserCell
s)中的行删除,这是基于点击{{{ 1}} UIButton
内部使用委托,这似乎是最干净的方式。
我按照这个例子:UITableViewCell Buttons with action
我有什么
UserCell类
UserCell
TableViewController类:
protocol UserCellDelegate {
func didPressButton(_ tag: Int)
}
class UserCell: UITableViewCell {
var delegate: UserCellDelegate?
let addButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add +", for: .normal)
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
addSubview(addButton)
addButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -6).isActive = true
addButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
addButton.heightAnchor.constraint(equalToConstant: self.frame.height / 2).isActive = true
addButton.widthAnchor.constraint(equalToConstant: self.frame.width / 6).isActive = true
}
func buttonPressed(_ sender: UIButton) {
delegate?.didPressButton(sender.tag)
}
}
其中class AddFriendsScreenController: UITableViewController, UserCellDelegate {
let cellId = "cellId"
var users = [User]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
cell.delegate = self
cell.tag = indexPath.row
return cell
}
func didPressButton(_ tag: Int) {
let indexPath = IndexPath(row: tag, section: 0)
users.remove(at: tag)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
中的User
附加了对视图控制器中数据库的调用。
我的问题
users
更新并且不会超出范围?即如果在索引0处删除了一行,则会删除" new"索引0处的行正常工作还是会删除索引1处的行?我想要什么
能够单击表格每行中的按钮,这会将其从桌面视图中删除。
我必须得到一些相当基本的错误,如果一个斯威夫特骑士可以启发我,我会非常感激。
非常感谢提前。
答案 0 :(得分:2)
您的代码中至少有3个问题:
UserCell
中,您应致电:button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
一旦您的单元格被实例化(例如,从init(style:reuseIdentifier:)
的实现中),以便self
引用UserCell
的实际实例。
AddFriendsScreenController
的{{1}}中,您正在设置单元格本身的标记(tableView(_:cellForRowAt:)
),但在您使用的cell.tag = indexPath.row
UserCell
buttonPressed(_:)
中按钮的标签。您应该将该功能修改为:func buttonPressed(_ sender: UIButton) {
//delegate?.didPressButton(sender.tag)
delegate?.didPressButton(self.tag)
}
indexPaths
不同步。理想情况下,您应该避免依赖索引路径来识别单元格,但这是另一个主题。 修改强>
避免标记与索引路径不同步的简单解决方案是将每个单元格与它们应该表示的User
对象相关联:
user
班级添加UserCell
媒体资源:class UserCell: UITableViewCell {
var user = User() // default with a dummy user
/* (...) */
}
User
内的正确tableView(_:cellForRowAt:)
对象://cell.tag = indexPath.row
cell.user = self.users[indexPath.row]
UserCellDelegate
协议方法的签名,以传递针对单元格存储的user
属性,而不是tag
:protocol UserCellDelegate {
//func didPressButton(_ tag: Int)
func didPressButtonFor(_ user: User)
}
UserCell
的{{1}}行动:buttonPressed(_:)
func buttonPressed(_ sender: UIButton) {
//delegate?.didPressButton(sender.tag)
//delegate?.didPressButton(self.tag)
delegate?.didPressButtonFor(self.user)
}
中,根据数据源中的AddFriendsScreenController
位置确定要删除的行:User
注意//func didPressButton(_ tag: Int) { /* (...) */ } // Scrap this.
func didPressButtonFor(_ user: User) {
if let index = users.index(where: { $0 === user }) {
let indexPath = IndexPath(row: index, section: 0)
users.remove(at: index)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
构造(optional binding)和三重if let index = ...
(identity operator)。
这种方法的缺点是它会在===
和User
类之间产生紧密耦合。最佳实践将指示使用更复杂的MVVM pattern,但这确实是另一个主题......
答案 1 :(得分:2)
网上有很多坏/旧代码,即使在SO上也是如此。你发布的内容有“不良做法”。所以先说几点:
UITableViewController
。有一个普通的视图控制器,上面有一个表格视图weak
,除非您100%确定自己在做什么private
,否则请使用fileprivate
。如果您100%确定它是您想要公开的值,则仅使用其余部分。以下是具有单个单元格类型的负责表格视图的示例,其中有一个按钮可在按下时删除当前单元格。创建新项目时,可以将整个代码粘贴到初始ViewController
文件中。在故事板中,表格视图添加了约束左,右,顶部,底部和视图控制器的出口。此外,还在表格视图中添加了一个单元格,其中包含一个指向单元格MyTableViewCell
的插座,其标识符设置为“MyTableViewCell”。
其余部分应在评论中解释。
class ViewController: UIViewController {
@IBOutlet private weak var tableView: UITableView? // By default use private and optional. Always. For all outlets. Only expose it if you really need it outside
fileprivate var myItems: [String]? // Use any objects you need.
override func viewDidLoad() {
super.viewDidLoad()
// Attach table viw to self
tableView?.delegate = self
tableView?.dataSource = self
// First refresh and reload the data
refreshFromData() // This is to ensure no defaults are visible in the beginning
reloadData()
}
private func reloadData() {
myItems = nil
// Simulate a data fetch
let queue = DispatchQueue(label: "test") // Just for the async example
queue.async {
let items: [String] = (1...100).flatMap { "Item: \($0)" } // Just generate some string
Thread.sleep(forTimeInterval: 3.0) // Wait 3 seconds
DispatchQueue.main.async { // Go back to main thread
self.myItems = items // Assign data source to self
self.refreshFromData() // Now refresh the table view
}
}
}
private func refreshFromData() {
tableView?.reloadData()
tableView?.isHidden = myItems == nil
// Add other stuff that need updating here if needed
}
/// Will remove an item from the data source and update the array
///
/// - Parameter item: The item to remove
fileprivate func removeItem(item: String) {
if let index = myItems?.index(of: item) { // Get the index of the object
tableView?.beginUpdates() // Begin updates so the table view saves the current state
myItems = myItems?.filter { $0 != item } // Update our data source first
tableView?.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade) // Do the table view cell modifications
tableView?.endUpdates() // Commit the modifications
}
}
}
// MARK: - UITableViewDelegate, UITableViewDataSource
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myItems?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as? MyTableViewCell {
cell.item = myItems?[indexPath.row]
cell.delegate = self
return cell
} else {
return UITableViewCell()
}
}
}
// MARK: - MyTableViewCellDelegate
extension ViewController: MyTableViewCellDelegate {
func myTableViewCell(pressedMainButton sender: MyTableViewCell) {
guard let item = sender.item else {
return
}
// Delete the item if main button is pressed
removeItem(item: item)
}
}
protocol MyTableViewCellDelegate: class { // We need ": class" so the delegate can be marked as weak
/// Called on main button pressed
///
/// - Parameter sender: The sender cell
func myTableViewCell(pressedMainButton sender: MyTableViewCell)
}
class MyTableViewCell: UITableViewCell {
@IBOutlet private weak var button: UIButton?
weak var delegate: MyTableViewCellDelegate? // Must be weak or we can have a retain cycle and create a memory leak
var item: String? {
didSet {
button?.setTitle(item, for: .normal)
}
}
@IBAction private func buttonPressed(_ sender: Any) {
delegate?.myTableViewCell(pressedMainButton: self)
}
}
在您的情况下,String
应替换为User
。接下来,您将进行一些更改,例如单元格中的didSet
(例如button?.setTitle(item.name, for: .normal)
),过滤方法应使用===
或比较某些id
或其他内容
答案 2 :(得分:0)
试试这个 -
更新didPressButton
方法,如下所示 -
func didPressButton(_ tag: Int) {
let indexPath = IndexPath(row: tag, section: 0)
users.remove(at: tag)
tableView.reloadData()
}