UITableView 中重复使用的单元格会导致发生不需要的操作
Reused Cells in UITableView Cause Unwanted Actions to Occur
我无法在网上找到答案,也不知道确切的问题,我也无法正确地表述问题。
我曾经有一个具有 "a custom UIView subview that contains a subview UIButton in x:2, y:2 coordinates" 的表格视图,并且该子视图 UIButton 有一个空白的星形图像,在用户触摸时切换为填充的星形图像。
图一:
图二:
它曾经工作得很好,直到我在这里和那里稍微修改了代码。
现在出现的问题是,当我触摸第1行的按钮时,例如,星号在第1行填满,但在第13行也填满。上图是单次触摸的结果"Acetate" 行旁边的星号。
有趣的是,当设备更改为 iPad Air 2 或具有其他屏幕尺寸的任何设备时,响应单元格会更改(例如,第 1 行响应第 22 行)。
我一直收到其他人的评论,说问题源于单元格被重用,但我对 UITableViewCell 缺乏了解,不知道发生了什么。我知道动态单元格是重复使用的,但这是否意味着某些单元格是彼此的克隆?
代码如下:
class WordListTableViewController: UITableViewController {
@IBOutlet weak var starButtonView: StarButton!
override func viewDidLoad() {
super.viewDidLoad()
//setting the wordList of wordListObject
ChemQuizCollection.wordListObject.quizList.sortInPlace()
ChemQuizCollection.formulaImages.sortInPlace()
tableView.estimatedRowHeight = 30
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 25
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("WordListTableCell", forIndexPath: indexPath) as! WordListTableViewCell
let row = indexPath.row
cell.starButtonView.buttonRow = row
cell.formulaLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.formulaLabel.text = ChemQuizCollection.wordListObject.quizList[row]
cell.formulaImage.image = UIImage(named: ChemQuizCollection.formulaImages[row])
if (FavoritesManager.favoritesList.objectForKey("\(row)") != nil) {
cell.starButtonView.buttonSelected = true
}
return cell
}
和
class StarButton: UIView {
var buttonRow : Int = 0
var buttonSelected : Bool = false
override init (frame : CGRect)
{
super.init(frame : frame)
initStar()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initStar()
}
func initStar() {
let filledStarImage = UIImage(named: "filledStar")
let emptyStarImage = UIImage(named: "emptyStar")
let button = UIButton(frame: CGRect(x: 2, y: 2, width: 33, height: 33))
button.userInteractionEnabled = true
button.addTarget(self, action: #selector(StarButton.fillingStar(_:)), forControlEvents: UIControlEvents.TouchUpInside)
button.setImage(emptyStarImage, forState: .Normal)
button.setImage(filledStarImage, forState: .Selected)
if buttonSelected == true {
button.selected = true
}
addSubview(button)
}
//Could have various errors
func fillingStar(sender: UIButton) {
if (sender.selected) == false {
FavoritesManager.favoritesList.setObject(ChemQuizCollection.wordListObject.quizList[buttonRow], forKey: "\(buttonRow)")
sender.selected = !sender.selected
FavoritesManager.favoritesList.synchronize()
} else {
FavoritesManager.favoritesList.removeObjectForKey("\(buttonRow)")
sender.selected = !sender.selected
FavoritesManager.favoritesList.synchronize()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
for view in subviews
{
view.touchesBegan(touches, withEvent: event)
}
}
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool
{
return true
}
和
struct FavoritesManager {
static var favoritesList = NSUserDefaults.standardUserDefaults()
static func resetFavorites() {
NSUserDefaults.standardUserDefaults().removePersistentDomainForName(NSBundle.mainBundle().bundleIdentifier!)
}
}
提前谢谢大家。学校马上就要开学了,这个问题已经困扰了我一个星期了,所以我很想得到任何帮助:)。如果有哪位能留个方法让我可以不断的向他请教,感激不尽
是的,UITableViewCells 被重用 - 这意味着您的单元格数量有限 - 当一个单元格从屏幕上消失时,另一个当前不可见的单元格将进入屏幕,使用 tableView:cellForRowAtIndexPath
在你的情况下,你应该使用 tableView:didSelectRowAtIndexPath
方法来触发单元格的状态更改,并响应此更改
通常,你应该避免使用按钮在细胞中,当它真的不可避免时
希望这可能有所帮助
为避免此问题,如果未在 cellForRowAtIndexPath 中选择按钮,则需要将星形图标设置为其默认状态。也就是说,要重复使用的单元格需要设置星号为默认状态。
您正在维护选定单元格的列表,因此当调用 cellForRowAtIndexPath 时,所有选定单元格都应设置为突出显示其星形图标,所有其他单元格应设置为默认图标。
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
if (FavoritesManager.favoritesList.objectForKey("\(row)") != nil) {
cell.starButtonView.buttonSelected = true
} else {
cell.starButtonView.buttonSelected = false
}
...
}
在包含按钮的星视图中,您需要根据您在上述方法中设置的内容将按钮设置为取消选择。
class StarButton: UIView {
...
func initStar(){
...
if buttonSelected == true {
button.selected = true
} else {
button.selected = false
}
...
}
...
}
我无法在网上找到答案,也不知道确切的问题,我也无法正确地表述问题。
我曾经有一个具有 "a custom UIView subview that contains a subview UIButton in x:2, y:2 coordinates" 的表格视图,并且该子视图 UIButton 有一个空白的星形图像,在用户触摸时切换为填充的星形图像。
图一:
图二:
它曾经工作得很好,直到我在这里和那里稍微修改了代码。
现在出现的问题是,当我触摸第1行的按钮时,例如,星号在第1行填满,但在第13行也填满。上图是单次触摸的结果"Acetate" 行旁边的星号。 有趣的是,当设备更改为 iPad Air 2 或具有其他屏幕尺寸的任何设备时,响应单元格会更改(例如,第 1 行响应第 22 行)。
我一直收到其他人的评论,说问题源于单元格被重用,但我对 UITableViewCell 缺乏了解,不知道发生了什么。我知道动态单元格是重复使用的,但这是否意味着某些单元格是彼此的克隆?
代码如下:
class WordListTableViewController: UITableViewController {
@IBOutlet weak var starButtonView: StarButton!
override func viewDidLoad() {
super.viewDidLoad()
//setting the wordList of wordListObject
ChemQuizCollection.wordListObject.quizList.sortInPlace()
ChemQuizCollection.formulaImages.sortInPlace()
tableView.estimatedRowHeight = 30
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 25
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("WordListTableCell", forIndexPath: indexPath) as! WordListTableViewCell
let row = indexPath.row
cell.starButtonView.buttonRow = row
cell.formulaLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
cell.formulaLabel.text = ChemQuizCollection.wordListObject.quizList[row]
cell.formulaImage.image = UIImage(named: ChemQuizCollection.formulaImages[row])
if (FavoritesManager.favoritesList.objectForKey("\(row)") != nil) {
cell.starButtonView.buttonSelected = true
}
return cell
}
和
class StarButton: UIView {
var buttonRow : Int = 0
var buttonSelected : Bool = false
override init (frame : CGRect)
{
super.init(frame : frame)
initStar()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initStar()
}
func initStar() {
let filledStarImage = UIImage(named: "filledStar")
let emptyStarImage = UIImage(named: "emptyStar")
let button = UIButton(frame: CGRect(x: 2, y: 2, width: 33, height: 33))
button.userInteractionEnabled = true
button.addTarget(self, action: #selector(StarButton.fillingStar(_:)), forControlEvents: UIControlEvents.TouchUpInside)
button.setImage(emptyStarImage, forState: .Normal)
button.setImage(filledStarImage, forState: .Selected)
if buttonSelected == true {
button.selected = true
}
addSubview(button)
}
//Could have various errors
func fillingStar(sender: UIButton) {
if (sender.selected) == false {
FavoritesManager.favoritesList.setObject(ChemQuizCollection.wordListObject.quizList[buttonRow], forKey: "\(buttonRow)")
sender.selected = !sender.selected
FavoritesManager.favoritesList.synchronize()
} else {
FavoritesManager.favoritesList.removeObjectForKey("\(buttonRow)")
sender.selected = !sender.selected
FavoritesManager.favoritesList.synchronize()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
for view in subviews
{
view.touchesBegan(touches, withEvent: event)
}
}
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool
{
return true
}
和
struct FavoritesManager {
static var favoritesList = NSUserDefaults.standardUserDefaults()
static func resetFavorites() {
NSUserDefaults.standardUserDefaults().removePersistentDomainForName(NSBundle.mainBundle().bundleIdentifier!)
}
}
提前谢谢大家。学校马上就要开学了,这个问题已经困扰了我一个星期了,所以我很想得到任何帮助:)。如果有哪位能留个方法让我可以不断的向他请教,感激不尽
是的,UITableViewCells 被重用 - 这意味着您的单元格数量有限 - 当一个单元格从屏幕上消失时,另一个当前不可见的单元格将进入屏幕,使用 tableView:cellForRowAtIndexPath
在你的情况下,你应该使用 tableView:didSelectRowAtIndexPath
方法来触发单元格的状态更改,并响应此更改
通常,你应该避免使用按钮在细胞中,当它真的不可避免时
希望这可能有所帮助
为避免此问题,如果未在 cellForRowAtIndexPath 中选择按钮,则需要将星形图标设置为其默认状态。也就是说,要重复使用的单元格需要设置星号为默认状态。
您正在维护选定单元格的列表,因此当调用 cellForRowAtIndexPath 时,所有选定单元格都应设置为突出显示其星形图标,所有其他单元格应设置为默认图标。
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
if (FavoritesManager.favoritesList.objectForKey("\(row)") != nil) {
cell.starButtonView.buttonSelected = true
} else {
cell.starButtonView.buttonSelected = false
}
...
}
在包含按钮的星视图中,您需要根据您在上述方法中设置的内容将按钮设置为取消选择。
class StarButton: UIView {
...
func initStar(){
...
if buttonSelected == true {
button.selected = true
} else {
button.selected = false
}
...
}
...
}