SearchBar、自定义单元格和 setCell 方法:致命错误:在展开可选值时意外发现 nil

Posted

技术标签:

【中文标题】SearchBar、自定义单元格和 setCell 方法:致命错误:在展开可选值时意外发现 nil【英文标题】:SearchBar, Custom Cell and setCell method: fatal error: unexpectedly found nil while unwrapping an Optional value 【发布时间】:2014-10-11 10:25:05 【问题描述】:

我在我的 tableView(在 MasterViewController 中)上实现了 searchBar 和 searchDisplayController。 我有一个 CustomCell 可以很好地显示我的 tableView 的单元格,但它不适用于我的搜索 tableView 的单元格,我不明白为什么。

这是我的 CustomCell 类:

import UIKit

class CustomCell: UITableViewCell 

@IBOutlet weak var pgrmLabel: UILabel!
@IBOutlet weak var noteLabel: UILabel!
@IBOutlet weak var logoView: UIImageView!


override func awakeFromNib() 
    super.awakeFromNib()
    // Initialization code


override func setSelected(selected: Bool, animated: Bool) 
    super.setSelected(selected, animated: animated)
    // Configure the view for the selected state


func setCell(pgrmName:String, noteInt:Int, logoName:String)
    println("setCell")
    if let itemName=self.pgrmLabel
        itemName.text=pgrmName
        println("pgrmName: \(pgrmName)")
    
    if let itemNote=self.noteLabel
        itemNote.text=String(noteInt)
    
    if let itemLogo=self.logoView
        itemLogo.image=UIImage(named: logoName)
    
    else
        println("setCell ELSE")
        self.pgrmLabel.text=pgrmName as String
        self.noteLabel.text=String(noteInt)
        self.logoView.image=UIImage(named: logoName as String)
       


还有masterViewController中的cellForRowAtIndexPath方法:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 

var program:Program
var cell: CustomCell!=tableView.dequeueReusableCellWithIdentifier("Cell") as CustomCell!

    if(cell==nil)
        println("cell nil")
        tableView.registerClass(CustomCell.classForCoder(), forCellReuseIdentifier: "Cell")
        cell=CustomCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
    
    else
    if tableView == searchDisplayController?.searchResultsTableView
        println("SearchCell")
        program=filteredPrograms[indexPath.row]
    
    else
        println("MasterCell")
        program=arrayOfPrograms[indexPath.row]
    
    cell.setCell(program.name as String, noteInt: program.note, logoName: program.imageName as String)
    
    return cell

另外,这是 Program 类:

import Foundation

class Program
    var name:String
    var note:Int
    var imageName:String
    var channel:String

init(name:String, note:Int, imageName:String, channel:String)
    self.name=name
    self.note=note
    self.imageName=imageName
    self.channel=channel


当我在 setCell 方法的 searchBar 中键入一个字母时,应用程序崩溃。它打印:

cell nil
SearchCell
setCell
setCell ELSE
fatal error: unexpectedly found nil while unwrapping an Optional value

如果有人知道发生了什么,那将非常有帮助。

非常感谢。

编辑:它不会崩溃,但如果我将 setCell 方法更改为此,我有一个空的搜索 tableView:

self.pgrmLabel?.text=pgrmName
self.noteLabel?.text=String(noteInt)
self.logoView?.image=UIImage(named: logoName)

pgrmName、noteInt 和 logoName 三个参数不是 nil,但 pgrmLabel、noteLabel 和 logoView 是。我猜(因为这些是可选值)我没有以正确的方式分配值。

【问题讨论】:

我终于用另一种方式做到了。 如果你想帮助别人,可能应该分享解决方案,否则这只是一个没有答案的问题(垃圾邮件),应该被删除。 【参考方案1】:

我终于做了另一种方法:我从 mainstoryboard 的 tableview 中删除了单元格,并创建了一个新文件(带有 xib 文件)来创建我的自定义单元格。

这是我的自定义类单元格的代码:

class TableCell: UITableViewCell 

@IBOutlet weak var pgrmLabel: UILabel!
@IBOutlet weak var noteLabel: UILabel!
@IBOutlet weak var logoView: UIImageView!

override func awakeFromNib() 
    super.awakeFromNib()
    // Initialization code


override func setSelected(selected: Bool, animated: Bool) 
    super.setSelected(selected, animated: animated)
    // Configure the view for the selected state


func setCell(cell: TableCell, pgrmName:String, noteInt:Int, logoName:String)
    if let itemName=self.pgrmLabel
        itemName.text=pgrmName
    
    if let itemNote=self.noteLabel
        itemNote.text="\(noteInt)"
    
    if let itemLogo=self.logoView
        itemLogo.image=UIImage(named: logoName)
        if ((itemLogo.image) == nil)
            itemLogo.image=UIImage(named: "placeholder.jpg")
        
    
  

我还创建了一个 BaseTableViewController 类,我的 MasterViewController(主 tableview)和我的 ResultsTableViewController(我也创建)将从它继承。

BaseTableViewController:

class BaseTableViewController: UITableViewController 
// MARK: Types

struct Constants 
    struct Nib 
        static let name = "TableCell"
    

    struct TableViewCell 
        static let identifier = "cellID"
    


// MARK: View Life Cycle

override func viewDidLoad() 
    super.viewDidLoad()

    let nib = UINib(nibName: Constants.Nib.name, bundle: nil)

    // Required if our subclasses are to use: dequeueReusableCellWithIdentifier:forIndexPath:
    tableView.registerNib(nib, forCellReuseIdentifier: Constants.TableViewCell.identifier)



// MARK:

func configureCell(cell: TableCell, forProgram program: Program) 
    cell.setCell(cell, pgrmName: program.name, noteInt: program.note, logoName: program.imageName)


override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat 
    return 100


override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat 
    return 100


主视图控制器:

class MasterViewController: BaseTableViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchDisplayDelegate, UISearchResultsUpdating 

struct RestorationKeys 
    static let viewControllerTitle = "ViewControllerTitleKey"
    static let searchControllerIsActive = "SearchControllerIsActiveKey"
    static let searchBarText = "SearchBarTextKey"
    static let searchBarIsFirstResponder = "SearchBarIsFirstResponderKey"


//controllers
var detailViewController: DetailViewController? = nil
var searchController: UISearchController!
var resultsTableController: ResultsTableViewController!
var restoredState=SearchControllerRestorableState()

//array of programs to display in the tableview
var arrayOfPrograms=[Program]()

struct SearchControllerRestorableState 
    var wasActive=false
    var wasFirstResponder=false


override func awakeFromNib() 
    super.awakeFromNib()
    if UIDevice.currentDevice().userInterfaceIdiom == .Pad 
        self.clearsSelectionOnViewWillAppear = false
        self.preferredContentSize = CGSize(width: 320.0, height: 600.0)
    


override func viewDidLoad() 
    super.viewDidLoad()
    //self.navigationItem.leftBarButtonItem = self.editButtonItem()
    if let split = self.splitViewController 
        let controllers = split.viewControllers
        self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
    
    self.setUpPrograms()

    let tableViewController=navigationController?.viewControllers[0] as MasterViewController

    resultsTableController=ResultsTableViewController()
    resultsTableController.tableView.delegate=self

    searchController=UISearchController(searchResultsController: resultsTableController)
    searchController.searchResultsUpdater=self
    searchController.searchBar.sizeToFit()
    tableView.tableHeaderView=searchController.searchBar
    searchController.delegate=self
    searchController.dimsBackgroundDuringPresentation=false
    searchController.searchBar.delegate=self
    definesPresentationContext=true

override func viewDidAppear(animated: Bool) 
    super.viewDidAppear(animated)

    // Restore the searchController's active state.
    if restoredState.wasActive 
        searchController.active = restoredState.wasActive
        restoredState.wasActive = false

        if restoredState.wasFirstResponder 
            searchController.searchBar.becomeFirstResponder()
            restoredState.wasFirstResponder = false
        
    


func searchBarSearchButtonClicked(searchBar: UISearchBar) 
    searchBar.resignFirstResponder()


override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) 
    var selectedProgram:Program

    if tableView == self.tableView 
        selectedProgram=arrayOfPrograms[indexPath.row]
    
    else
        selectedProgram=resultsTableController.filteredPrograms[indexPath.row]
    

    let detailViewController=DetailViewController.forProgram(selectedProgram)
    navigationController!.pushViewController(detailViewController, animated: true)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)


// MARK: - Table View

override func numberOfSectionsInTableView(tableView: UITableView) -> Int 
    return 1


override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return arrayOfPrograms.count




func updateSearchResultsForSearchController(searchController: UISearchController) 
    let searchResults=arrayOfPrograms

    // Strip out all the leading and trailing spaces.
    let whitespaceCharacterSet = NSCharacterSet.whitespaceCharacterSet()
    let strippedString = searchController.searchBar.text.stringByTrimmingCharactersInSet(whitespaceCharacterSet)
    let searchItems = strippedString.componentsSeparatedByString(" ") as [String]

    // Build all the "AND" expressions for each value in the searchString.
    var andMatchPredicates = [NSPredicate]()

    for searchString in searchItems 
        // Each searchString creates an OR predicate for: name, yearIntroduced, introPrice.
        //
        // Example if searchItems contains "iphone 599 2007":
        //      name CONTAINS[c] "iphone"
        //      name CONTAINS[c] "599", yearIntroduced ==[c] 599, introPrice ==[c] 599
        //      name CONTAINS[c] "2007", yearIntroduced ==[c] 2007, introPrice ==[c] 2007
        //
        var searchItemsPredicate = [NSPredicate]()

        // Name field matching.
        var lhs = NSExpression(forKeyPath: "name")
        var rhs = NSExpression(forConstantValue: searchString)
        var finalPredicate = NSComparisonPredicate(leftExpression: lhs, rightExpression: rhs, modifier: .DirectPredicateModifier, type: .ContainsPredicateOperatorType, options: .CaseInsensitivePredicateOption)
        searchItemsPredicate.append(finalPredicate)

        // Channel field matching.
        lhs = NSExpression(forKeyPath: "channel")
        rhs = NSExpression(forConstantValue: searchString)
        finalPredicate = NSComparisonPredicate(leftExpression: lhs, rightExpression: rhs, modifier: .DirectPredicateModifier, type: .ContainsPredicateOperatorType, options: .CaseInsensitivePredicateOption)
        searchItemsPredicate.append(finalPredicate)

        // Add this OR predicate to our master AND predicate.
        let orMatchPredicates = NSCompoundPredicate.orPredicateWithSubpredicates(searchItemsPredicate)
        andMatchPredicates.append(orMatchPredicates)
    

    // Match up the fields of the Product object.
    let finalCompoundPredicate = NSCompoundPredicate.andPredicateWithSubpredicates(andMatchPredicates)

    let filteredResults = searchResults.filter  finalCompoundPredicate.evaluateWithObject($0) 

    // Hand over the filtered results to our search results table.
    let resultsController = searchController.searchResultsController as ResultsTableViewController
    resultsController.filteredPrograms = filteredResults
    resultsController.tableView.reloadData()


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCellWithIdentifier(Constants.TableViewCell.identifier, forIndexPath: indexPath) as TableCell
    let program=arrayOfPrograms[indexPath.row]
    configureCell(cell, forProgram: program)
    return cell


override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool 
    // Return false if you do not want the specified item to be editable.
    return false 


override func encodeRestorableStateWithCoder(coder: NSCoder) 
    super.encodeRestorableStateWithCoder(coder)

    // Encode the title.
    coder.encodeObject(navigationItem.title!, forKey:RestorationKeys.viewControllerTitle)

    // Encode the search controller's active state.
    coder.encodeBool(searchController.active, forKey:RestorationKeys.searchControllerIsActive)

    // Encode the first responser status.
    coder.encodeBool(searchController.searchBar.isFirstResponder(), forKey:RestorationKeys.searchBarIsFirstResponder)

    // Encode the search bar text.
    coder.encodeObject(searchController.searchBar.text, forKey:RestorationKeys.searchBarText)


override func decodeRestorableStateWithCoder(coder: NSCoder) 
    super.decodeRestorableStateWithCoder(coder)

    // Restore the title.
    if let decodedTitle = coder.decodeObjectForKey(RestorationKeys.viewControllerTitle) as? String 
        title = decodedTitle
    
    else 
        fatalError("A title did not exist. In your app, handle this gracefully.")
    

    // Restore the active state:
    // We can't make the searchController active here since it's not part of the view
    // hierarchy yet, instead we do it in viewWillAppear.
    //
    restoredState.wasActive = coder.decodeBoolForKey(RestorationKeys.searchControllerIsActive)

    // Restore the first responder status:
    // Like above, we can't make the searchController first responder here since it's not part of the view
    // hierarchy yet, instead we do it in viewWillAppear.
    //
    restoredState.wasFirstResponder = coder.decodeBoolForKey(RestorationKeys.searchBarIsFirstResponder)

    // Restore the text in the search field.
    searchController.searchBar.text = coder.decodeObjectForKey(RestorationKeys.searchBarText) as String



ResultsTableViewController

class ResultsTableViewController: BaseTableViewController 

var filteredPrograms=[Program]()

override func viewDidLoad() 
    super.viewDidLoad()
    println("results table view")


override func didReceiveMemoryWarning() 
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.


override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    println("results table view nb of raws in section")
    return filteredPrograms.count


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 
    //let cell=tableView.dequeueReusableCellWithIdentifier(Constants.TableViewCell.identifier) as TableCell!
    let cell = tableView.dequeueReusableCellWithIdentifier(Constants.TableViewCell.identifier, forIndexPath: indexPath) as TableCell
    let program = filteredPrograms[indexPath.row]
    configureCell(cell, forProgram: program)
    return cell


【讨论】:

感谢您的回答。它以许多其他解决方案没有的方式解决了我的问题。我不知道为什么它不起作用,但是将我的 UITableViewCell 拆分为它自己的 NIB 就可以了。

以上是关于SearchBar、自定义单元格和 setCell 方法:致命错误:在展开可选值时意外发现 nil的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 错误的图像加载了自定义单元格和 UIView

Swift - 从自定义单元格和单元格数据传递到另一个控制器

使用 Swift 4 搜索自定义表格单元格和过滤器

iPhone-如何将 Storyboard 与自定义 UITableView 单元格和 CellWithIdentifier 一起使用

如何在同一个xib中放置自定义单元格和表格视图?

使用 nib 自定义单元格和自动布局滚动跳转(ios 8 swift - UITableview)