UITableViewController的Cell的内容消失

时间:2017-07-19 20:02:20

标签: ios uitableview controller uilabel

我正在制作一个硬币收集应用程序,它应该可以帮助用户保存他们收藏的便携式记录。到目前为止,有两个视图控制器:CoinTableViewController表示所有硬币类别的表格视图,CoinCategoryViewController表示该类别中特定硬币的表视图。

我显然希望我的硬币可以根据多个标准(例如同一年,同一个国家等等)进行报告。为此,我创建了一个名为CoinTableViewCell的可重用的tableview单元子类,它有5个UILabel,表示集合中的硬币可按类别分组的所有信息。

这是我的故事板的样子

CoinTableViewController storyboard with labels

逻辑是,根据我当前的排序标准,我可以隐藏单元格中的某些标签,以反映硬币排序的标准。设置轮打开一个显示在屏幕左侧的菜单,其中包含如何对硬币进行排序的选项,如果排序选项已更改,关闭菜单会使Coin Categories控制器在集合中使用硬币。< / p>

Example of Menu

我的问题是,虽然我的程序整体运行,但有时候,一些单元格在程序转换后不会完全出现(在用户打开菜单并选择一个选项之后)。

这是这样的: problematic table view controller

正如您所看到的,视图控制器中的底部两个单元格缺少顶部标签,即使其他单元格具有它们也是如此。由于我已经将tableview控制器的单元格实现为可调整大小,因此表格视图单元格应自动调整大小以适应内部的内容。

这是我的代码:

//  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对象的集合,它们具有属性“Index”(用于它们在uitableviewcontroller中的位置)和一个CoinCategory对象,它包含硬币类的对象。

我一直试图调试这几天,我不知道出了什么问题。有人可以帮忙吗?

许多人提前感谢,祝你有个美好的一天!

2 个答案:

答案 0 :(得分:0)

我猜CategoryType中的category.coinCategory?.currentCategoryType searchResults coinsByCategory / COUNTRY_VALUE_AND_CURRENCY中的所有对象都不相同。

即。第二个和第三个对象的currentCategoryTypeCOUNTRY,第四个和第五个对象的currentCategoryTypesearchResults

您能确认coinsByCategory / iterator中的所有对象const vector<typename iterator_traits<Iter>::value_type> temp(beg, end); 是否相同?

答案 1 :(得分:0)

我想我现在知道错误是什么:

在我的configureLabelsForCategoryType类的CoinTableViewCell方法中,我在valueAndDenomination标签上调用了removeFromSuperView()方法,该方法具有从单元格中永久删除该标签的效果。

因此,细胞没有那个标签,即使它后来被重新用于另一个CoinCategory。

我将valueAndDenominationLabel.removeFromSuperView()行替换为valueAndDenominationLabel.isHidden = true,这样可以隐藏用户的valueAndDenominationLabel,但不会将其从单元格中永久删除。

许多人感谢那些花时间阅读我的问题并作出回应的人。 你的努力帮助我思考我的问题并追踪它!

谢谢!