UITableViewController 的单元格内容消失
Posted
技术标签:
【中文标题】UITableViewController 的单元格内容消失【英文标题】:UITableViewController's Cell's Content disappearing 【发布时间】:2017-07-19 20:02:20 【问题描述】:我正在制作一个硬币收集应用程序,该应用程序旨在帮助用户维护其收藏的便携式记录。到目前为止,有两个视图控制器:CoinTableViewController
提供所有硬币类别的表格视图,CoinCategoryViewController
应该具有该类别中特定硬币的表格视图。
我显然希望我的代币能够根据多个标准(例如同一年、同一国家等)进行报告。为此,我创建了一个名为 CoinTableViewCell
的可重用 tableview 单元子类,它有 5 个 UILabel,它们代表收藏中的硬币可以按类别分组的所有信息。
这是我的故事板的样子
逻辑是根据我当前的排序标准,我可以隐藏单元格中的某些标签以反映硬币排序的标准。设置轮会打开一个菜单,该菜单出现在屏幕左侧,其中包含有关如何对硬币进行分类的选项,如果更改分类选项,则关闭菜单会使硬币类别控制器重新使用收藏中的硬币。
我的问题是,虽然我的程序总体上可以运行,但有时,某些单元格在程序使用它们后(在用户打开菜单并选择一个选项后)并没有完全显示。
这是这样的:
如您所见,视图控制器中的底部两个单元格缺少顶部标签,即使其他单元格有它们。由于我已经将 tableview 控制器的单元格实现为可调整大小,因此 table view 单元格应该自动调整大小以适应里面的内容。
这是我的代码:
// Controls the table view controller showing the general coins (one per each category)
import UIKit
import CoreData
class CoinTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchResultsUpdating, UITabBarControllerDelegate
//this is an array of all the coins in the collection
//each row of this two-dimensional array represents a new category
var coinsByCategory: [CoinCategoryMO] = []
var fetchResultController: NSFetchedResultsController<CoinCategoryMO>!
//we sort the coins by the category and then display them in the view controller
//example includes [ [Iraq Dinar 1943, Iraq Dinar 1200], etc. etc.]
//<OTHER VARIABLES HERE>
//the data here is used for resorting the coins into their respective categories
//the default sorting criteria is sorting the coins into categories with the same country, value, and currency
//and the user can change the app's sorting criteria by opening the ConfiguringPopoverViewController and changing the sorting criteria there
private var isCurrentlyResortingCoinsIntoNewCategories : Bool = false
override func viewDidLoad()
super.viewDidLoad()
self.tabBarController?.delegate = self
//we now fetch the data
let fetchRequest : NSFetchRequest<CoinCategoryMO> = CoinCategoryMO.fetchRequest()
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
let context = appDelegate.persistentContainer.viewContext
let sortDescriptor = NSSortDescriptor(key: "index", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchResultController.delegate = self
do
try fetchResultController.performFetch()
if let fetchedObjects = fetchResultController.fetchedObjects
self.coinsByCategory = fetchedObjects
catch
print(error)
//if there is an empty area in the table view, instead of showing
//empty cells, we show a blank area
self.tableView.tableFooterView = UIView()
//we configure the row heights for the table view so that the cells are resizable.
//ALSO: should the user want to adjust the text size in "General"->"Accessibility"
//the text size in the app will be automatically adjusted for him...
tableView.estimatedRowHeight = 120
tableView.rowHeight = UITableViewAutomaticDimension
//WE CONFIGURE THE SEARCH BAR AND NAVIGATION BAR....
//if the user scrolls up, he sees a white background, not a grey one
tableView.backgroundView = UIView()
override func numberOfSections(in tableView: UITableView) -> Int
return fetchResultController.sections!.count
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
if searchController != nil && searchController.isActive
return searchResults.count
else
if let sections = fetchResultController?.sections
return sections[section].numberOfObjects
else
return 0
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
//configure the cell
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CoinTableViewCell
//Initialize the Cell
let category = (searchController != nil && searchController.isActive) ? searchResults[indexPath.row] : coinsByCategory[indexPath.row]
//we now remove the extra labels that we do not need
cell.configureLabelsForCategoryType(theType: (category.coinCategory?.currentCategoryType)!)
let sampleCoin : Coin = category.coinCategory!.getCoin(at: 0)!
cell.countryLabel.text = "Country: \(sampleCoin.getCountry())"
cell.valueAndDenominationLabel.text = "Value & Denom.: \(sampleCoin.valueAndDenomination)"
//now we add in the quantity
cell.quantityLabel.text = "Quantity: \(String(describing: coinsByCategory[indexPath.row].coinCategory!.count))"
//we now add in the denomination
cell.denominationOnlyLabel.text = "Denom.: \(sampleCoin.getDenomination())"
//we now add in the year
if sampleCoin.getYear() == nil
cell.yearLabel.text = "Year: " + (Coin.DEFAULT_YEAR as String)
else
let yearABS = abs(Int32(sampleCoin.getYear()!))
cell.yearLabel.text = "Year: \(yearABS) \(sampleCoin.getYear()!.intValue > 0 ? TimePeriods.CE.rawValue : TimePeriods.BCE.rawValue)"
//we add in an accessory to indicate that clicking this cell will result in more information
cell.accessoryType = .disclosureIndicator
return cell
func deleteCoinCategory(rowPath: IndexPath)
if 0 <= rowPath.row && rowPath.row < self.coinsByCategory.count
//we have just tested that the rowPath index is valid
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
let context = appDelegate.persistentContainer.viewContext
let coinCategoryToDelete = self.fetchResultController.object(at: rowPath)
context.delete(coinCategoryToDelete)
appDelegate.saveContext()
//ok we now deleted the category, now we update the indices
updateIndices()
appDelegate.saveContext()
func deleteCoin(c: Coin, indexOfSelectedCategory: IndexPath) -> Bool
//we have a coin that we want to delete from this viewcontroller
//and the data contained in it.
//
//the parameter indexOfSelectedCategory refers to the IndexPath of the
//row in the TableView contained in THIS viewcontroller whose category
//of coins we are modifying in this method
//
//Return value: a boolean that indicates whether a single coin has
//been deleted - meaning that the user should return to the parentviewcontroller
if 0 < indexOfSelectedCategory.row && indexOfSelectedCategory.row < self.coinsByCategory.count && self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.hasCoin(c: c) == true
//the index is valid as it refers to a category in the coinsByCategory array
//and the examined category has the coin in question
if self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.count == 1
//the coin "c" that we are going to delete is the only coin in the entire category
//we reduce the problem to a simpler one that has been already solved (thanks mathematicians!)
self.deleteCoinCategory(rowPath: indexOfSelectedCategory)
return true
else
//there is more than one coin in the category
self.coinsByCategory[indexOfSelectedCategory.row].coinCategory?.removeCoin(c: c)
//we save the changes in the database...
if let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
appDelegate.saveContext()
return false
return false
func addCoin(coinToAdd: Coin)
//we check over each category to see if the coin can be added
var addedToExistingCategory: Bool = false
if let appDelegate = UIApplication.shared.delegate as? AppDelegate
for i in 0..<self.coinsByCategory.count
if self.coinsByCategory[i].coinCategory?.coinFitsCategory(aCoin: coinToAdd) == true
//we can add the coin to the category
self.coinsByCategory[i].coinCategory = CoinCategory(coins: self.coinsByCategory[i].coinCategory!.coinsInCategory+[coinToAdd], categoryType: coinsByCategory[i].coinCategory!.currentCategoryType)
addedToExistingCategory = true
break
if addedToExistingCategory == false
//since the coinToAdd does not fall in the existing categories, we create a new one
let newCategory = CoinCategoryMO(context: appDelegate.persistentContainer.viewContext)
newCategory.coinCategory = CoinCategory(coins: [coinToAdd], categoryType: CoinCategory.CategoryTypes.getTheCategoryFromString(str: UserDefaults.standard.object(forKey: "currentSortingCriteria") as! NSString).rawValue)
//this index indicates that we are going to insert this newCategory into index "0" of all the categories in the table
newCategory.index = 0
appDelegate.saveContext()
//now since we have added the coin, we now updated the indices of each CoinCategoryMO object
updateIndices()
func coinFitsExistingCategory(coin: Coin) -> Bool
//this function checks if the coin can be added to the existing categories
for i in 0..<self.coinsByCategory.count
if self.coinsByCategory[i].coinCategory?.coinFitsCategory(aCoin: coin) == true
//we can add the coin to the category
return true
return false
func resortCoinsInNewCategories(newCategorySetting : CoinCategory.CategoryTypes?)
//we want to resort all the coins in the category by new sorting criteria
if newCategorySetting != nil && newCategorySetting! != CoinCategory.CategoryTypes.getTheCategoryFromString(str: UserDefaults.standard.object(forKey: "currentSortingCriteria") as! NSString)
//We have a valid CoinCategory.CategoryTypes sorting criteria that is different from the one currently used.
//We resort the coins in the collection by the new category
UserDefaults.standard.setValue(newCategorySetting!.rawValue, forKey: "currentSortingCriteria")
if self.coinsByCategory.count != 0
//we actually have some coins to resort... let's get to work!
self.isCurrentlyResortingCoinsIntoNewCategories = true
//we first get an array of all the coins in existing categories
var allCoinsArray : [Coin] = []
for i in 0..<self.coinsByCategory.count
allCoinsArray += self.coinsByCategory[i].coinCategory!.coinsInCategory
//now we need to delete all the categories in existence...
let firstCategoryIndexPath = IndexPath(row: 0, section: 0)
let numberOfCategoriesToDelete = self.coinsByCategory.count
for _ in 0..<numberOfCategoriesToDelete
self.deleteCoinCategory(rowPath: firstCategoryIndexPath)
//OK... now that we have deleted all old categories... it is time to start to create new ones...
for i in 0..<allCoinsArray.count
//AND we add the coin to the array!
//this function also automatically updates the indices, so it is not an issue there
self.addCoin(coinToAdd: allCoinsArray[i])
//we are done resorting
self.isCurrentlyResortingCoinsIntoNewCategories = false
private func updateIndices()
//this function updates the "index" property so that
//each CoinCategoryMO object in the coinsByCategory array
//has an index corresponding to its position.
//After this function is called, we must save the core data in the AppDelegate.
//
//This function is called ONLY after the changes to the CoinCategoryMO objects
//are saved in core data and the self.coinsByCategory array is updated to have
//the latest version of the data
for i in 0..<self.coinsByCategory.count
//the only reason why we create an entirely new CoinCategory object
//is that the creation of an entirely new CoinCategory object
//is the only way that the appDelegate will save the information
self.coinsByCategory[i].index = Int16(i)
if let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate.saveContext()
//these delegate methods control the core data database
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
tableView.beginUpdates()
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
switch type
case .insert :
if let newIndexPath = newIndexPath
tableView.insertRows(at: [newIndexPath], with: .fade)
case .delete:
if let indexPath = indexPath
tableView.deleteRows(at: [indexPath], with: .fade)
case .update:
if let indexPath = indexPath
tableView.reloadRows(at: [indexPath], with: .fade)
default:
tableView.reloadData()
if let fetchedObjects = controller.fetchedObjects
self.coinsByCategory = fetchedObjects as! [CoinCategoryMO]
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
tableView.endUpdates()
if self.isCurrentlyResortingCoinsIntoNewCategories != true
//we let the user know if the collection is empty
if self.coinsByCategory.count == 0
self.messageUserIfCollectionEmpty()
else
self.activateCollectionEmptyLabel(newState: false)
然后我的 CoinTableViewCell 类是:
// Represents a cell of the coin buttons
import UIKit
class CoinTableViewCell: UITableViewCell
@IBOutlet var countryLabel: UILabel!
@IBOutlet var valueAndDenominationLabel: UILabel!
@IBOutlet var quantityLabel: UILabel!
@IBOutlet var denominationOnlyLabel : UILabel!
@IBOutlet var yearLabel : UILabel!
override func awakeFromNib()
super.awakeFromNib()
// Initialization code
func restoreAllLabelsToCell()
//this is a function that is called when this cell is being initialized in the cellForRowAt method in a tableview..
//we want to make all the labels visible so that the previous usage of a reusable tableview cell does not affect this usage of the cell
countryLabel.isHidden = false
valueAndDenominationLabel.isHidden = false
quantityLabel.isHidden = false
denominationOnlyLabel.isHidden = false
yearLabel.isHidden = false
func configureLabelsForCategoryType(theType : NSString)
//in this function, we remove all the extra labels
//that contain information that does not relate to the general type of the category from the stack view
//For example, the year label is removed when the category is a country, as a year does not determine what category a coin falls into.
//we restore all the labels in this cell as we do not want the reusable cell's past usage
//which may have lead to a label dissappearing to carry over into this new usage of the cell
self.restoreAllLabelsToCell()
switch theType
case CoinCategory.CategoryTypes.COUNTRY_VALUE_AND_CURRENCY.rawValue:
//we do not need information about the coin's denomination (without its value) or the year
denominationOnlyLabel.isHidden = true
yearLabel.isHidden = true
//after we remove the labels, we now make the first label bold and black
valueAndDenominationLabel.font = UIFont.boldSystemFont(ofSize: self.valueAndDenominationLabel.font.pointSize)
valueAndDenominationLabel.textColor = UIColor.black
case CoinCategory.CategoryTypes.COUNTRY.rawValue:
//we do not need the information about the coin's value and denominations nor year
valueAndDenominationLabel.isHidden = true
denominationOnlyLabel.isHidden = true
yearLabel.isHidden = true
//after we remove the labels, we make the first label bold and black
countryLabel.font = UIFont.boldSystemFont(ofSize: self.countryLabel.font.pointSize)
countryLabel.textColor = UIColor.black
case CoinCategory.CategoryTypes.CURRENCY.rawValue:
//we do not information about the coin's value & denomination (together, that is), or year
valueAndDenominationLabel.isHidden = true
yearLabel.isHidden = true
//after we remove the labels, we make the first label bold and black
denominationOnlyLabel.font = UIFont.boldSystemFont(ofSize: self.denominationOnlyLabel.font.pointSize)
denominationOnlyLabel.textColor = UIColor.black
case CoinCategory.CategoryTypes.YEAR.rawValue:
//we do not information about the coin's value, denomination, or country
valueAndDenominationLabel.removeFromSuperview()
denominationOnlyLabel.isHidden = true
countryLabel.isHidden = true
//after we remove the labels, we make the first label bold and black
yearLabel.font = UIFont.boldSystemFont(ofSize: self.yearLabel.font.pointSize)
yearLabel.textColor = UIColor.black
default:
//the string does not match any of the categories available
//we do not remove any labels
break
我的 CoreData 实现是 CoinCategoryMO 对象的集合,这些对象具有“索引”属性(用于它们在 uitableviewcontroller 中的位置)和一个包含 Coin 类对象的 CoinCategory 对象。
我已经尝试调试了好几天了,但我不知道出了什么问题。有人可以帮忙吗?
非常感谢,祝您有美好的一天!
【问题讨论】:
【参考方案1】:我猜,category.coinCategory?.currentCategoryType
中的 CategoryType
在 searchResults
/ coinsByCategory
中的所有对象中都不相同。
即第二个和第三个对象有COUNTRY_VALUE_AND_CURRENCY
作为它的currentCategoryType
,第四个和第五个有COUNTRY
。
您能否确认currentCategoryType
对于searchResults
/ coinsByCategory
中的所有对象都相同?
【讨论】:
您好,感谢您的帮助。是的,我可以确认 searchResults / coinByCategory 中的所有对象的 currentCategoryType 都是相同的,因为我在初始化类别常量后在 cellForRowAt 方法的开头放入了print("DEBUG: the category's current category type is: \(category.coinCategory?.currentCategoryType)")
语句。在我使用类别中的硬币并看到与我在问题中发布的相同图形结果后,我检查了控制台,所有类别都具有相同的当前类别类型。 :-(。我现在不知道该怎么办。有什么想法吗?谢谢!
有时,即使我将新硬币添加到新类别中,tableview 的第一行中也有一个未填充的单元格,缺少一些标签。
当我使用“按国家、价值和面额排序”排序标准时,似乎出现了 tableview 控制器行的这个问题。【参考方案2】:
我想我现在知道错误是什么了:
在我的CoinTableViewCell
类的configureLabelsForCategoryType
方法中,我在valueAndDenomination 标签上调用了removeFromSuperView()
方法,这具有从单元格中永久删除该标签的效果。
因此该单元格没有该标签,即使它后来被用于另一个 CoinCategory。
我将valueAndDenominationLabel.removeFromSuperView()
行替换为valueAndDenominationLabel.isHidden = true
,其效果是对用户隐藏valueAndDenominationLabel
,但不会将其从单元格中永久删除。
非常感谢那些花时间阅读我的问题并做出回应的人。 你的努力帮助我思考了我的问题并找到了答案!
谢谢!
【讨论】:
以上是关于UITableViewController 的单元格内容消失的主要内容,如果未能解决你的问题,请参考以下文章
UITableViewController - 单元格大小分隔符和背景色
回退时 UITableViewController 单元格保持突出显示
如何将单元格插入 UITableViewController
UITableViewController 的单元格内容消失