【问题标题】:Swift Xcode 13 Programmatic UITableViewController nil delegateSwift Xcode 13 编程 UITableViewController nil 委托
【发布时间】:2020-06-13 15:43:50
【问题描述】:

Delegated 函数触发但由于它的 nil 而崩溃,items 数组中的对象由 CoreData 填充,这个 var model: CoreDataModel = CoreDataModel(CoreDataController.shared) 必须被实例化,而不是像 viewDidLoad 中预期的那样,以防止表视图行计数出现 nil 错误(model.items.count)

在启动时,items 数组是完整的 Sqlite DB,搜索它的表的子集并打印到控制台证明数组已更改并且只有专辑的子集。

基础视图控制器

import UIKit
import CoreData

protocol UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
}

protocol MasterModel {
    var client: LastFMClient { get }
    func searchFeed(with userSearchTerm: String?, completion: @escaping (Bool) -> Void)
}

protocol DataReloadTableViewDelegate: class {
    func reloadAlbumsTable()
}

class BaseViewController: UITableViewController, MasterModel {

    let cellId = "sdlfjowieurewfn3489844224947824dslaksjfs;ad"
    let logoContainer = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
    let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
    let image = UIImage(named: "lastFMRedBlack")
    let searchBar = UISearchBar()
    let client = LastFMClient()
    var model: CoreDataModel = CoreDataModel(CoreDataController.shared)
    private var searchResults: Root?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupSearchController()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
        tableView.register(SubtitleTableViewCell.self, forCellReuseIdentifier: cellId)
        tableView.tableFooterView = UIView(frame: CGRect.zero)
        tableView.separatorColor = UIColor(red: 72.5/255, green: 0/255, blue: 0/255, alpha: 1)

        imageView.contentMode = .scaleAspectFit
        imageView.image = image
        logoContainer.addSubview(imageView)
        navigationItem.titleView = logoContainer

        print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))

        model.delegate = self
        model.fetchAllAlbums()
    }

    // MARK - SearchBar
    private func setupSearchController() {

        searchBar.sizeToFit()
        searchBar.placeholder = "Search for Album"
        searchBar.delegate = self
        showSearchBarButton(shouldShow: true)
    }

    func showSearchBarButton (shouldShow: Bool) {

        if shouldShow {
            navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(handleShowSearchBar))

        } else {
            searchBar.showsCancelButton = true
            navigationItem.rightBarButtonItem = nil
        }
    }

    func search(shouldShow: Bool) {
        showSearchBarButton(shouldShow: !shouldShow)
        navigationItem.titleView = shouldShow  ? searchBar : logoContainer
    }


    @objc func handleShowSearchBar(){

        search(shouldShow: true)
        searchBar.becomeFirstResponder()
    }


    // MARK - API Request
    func searchFeed(with userSearchTerm: String?, completion: @escaping (Bool) -> Void) {

        // Use the API to get data
        client.getFeed(from: LastFMRequest.albumSearch(userSearchTerm: userSearchTerm) ) { result in

            switch result {
            case .success(let data):

                do {
                    let data = try DataParser.parse(data, type: RootNode.self)

                    self.searchResults = data.results
                    completion(true)

                } catch {
                    print(error.localizedDescription)
                    completion(false)
                }

            case .failure(let error):
                print(error.localizedDescription)
                completion(false)
            }
        }
    }
}


extension BaseViewController: UISearchBarDelegate {

    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        searchBar.text = nil
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {

        search(shouldShow:  false)
    }

    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {

        guard let searchTextString = searchBar.text else { return }

        searchFeed(with: searchTextString.replacingOccurrences(of: " ", with: "+").lowercased(), completion: {_ in

            if self.searchResults!.albumMatches.album.count == 0 {

                DispatchQueue.main.async {
                    let alertController = UIAlertController(title: "No Albums Found", message: "Try Another Keyword(s)", preferredStyle: .alert)
                    let OKAction = UIAlertAction(title: "OK", style: .default) { action in
                        print("Pressed OK")
                    }
                    alertController.addAction(OKAction)
                    self.present(alertController, animated: true, completion: nil)
                }

            } else {

                let dataManager = DataManager(data: self.searchResults!)
                do {
                    try dataManager.saveData()

                } catch {

                    print(error)
                }

            }
        })

        search(shouldShow:  false)
        searchBar.resignFirstResponder()
    }
}

class SubtitleTableViewCell: UITableViewCell {

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension BaseViewController: UITableViewDataSource {

    var numberOrSections: Int { return 1 }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        guard section >= 0 && section < numberOrSections else { return 0 }

        return model.items.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)

        let albumItem = model.items[indexPath.row]
        cell.textLabel?.text = albumItem.value(forKeyPath: "name") as? String
        cell.detailTextLabel?.text = albumItem.value(forKeyPath: "artist") as? String

        cell.accessoryType = .disclosureIndicator
        // Populate the cell from the object
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        let vc = DetailViewController()
        let albumItem = model.items[indexPath.row]
        vc.iamgeURL = albumItem.value(forKeyPath: "imageUrl") as? String
        vc.albumName = albumItem.value(forKeyPath: "name") as? String
        navigationController?.pushViewController(vc, animated: true)
    }


}

extension BaseViewController: DataReloadTableViewDelegate {

    func reloadAlbumsTable(){
        DispatchQueue.main.async {
            print(self.model.items.count)
            self.tableView.reloadData()
        }
    }  
}

核心数据模型

导入基础 导入核心数据

类 CoreDataModel {

weak var delegate: DataReloadTableViewDelegate?
let coreDataController: CoreDataController
var items:[Albums] = []

init(_ coreDataController: CoreDataController) {
    self.coreDataController = coreDataController
    self.coreDataController.mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}

internal func saveSearchAlbums(responseData: Root) throws {

    let newSearch = Searches(context: coreDataController.mainContext)

    newSearch.searchQuery = responseData.attr.forField

    for (_, element) in responseData.albumMatches.album.enumerated() {

        let newAlbum = Albums(context: coreDataController.mainContext)

        let artistName = element.artist
        let albumName = element.name
        let imageUrlTwo = element.image[2].text
        let imageUrlZero = element.image[0].text
        let imageUrlOne = element.image[1].text

        var imageUrl: String = ""

        if !JustLetters.blank(text: imageUrlTwo) {
            imageUrl = imageUrlTwo
        }

        if !JustLetters.blank(text: imageUrlZero) {
            imageUrl = imageUrlZero
        }

        if !JustLetters.blank(text: imageUrlOne) {
            imageUrl = imageUrlOne
        }

        if !JustLetters.blank(text: artistName) && !JustLetters.blank(text: albumName) && !JustLetters.blank(text: imageUrl) {

            newAlbum.searches = newSearch
            newAlbum.artist = artistName
            newAlbum.name = albumName
            newAlbum.imageUrl = imageUrl

            newSearch.addToAlbums(newAlbum)
        }

    }
    // Save context
    coreDataController.saveContext()
    fetchAlbumsByKeyword(searchTerm: responseData.attr.forField)
}

internal func fetchAlbumsByKeyword(searchTerm: String) {

    // Create Fetch Request
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Albums")

    // Add Sort Descriptor
    let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]

    // Add Predicate
    let predicate = NSPredicate(format: "name CONTAINS[c] %@", searchTerm)
    fetchRequest.predicate = predicate

    do {
        items = try  coreDataController.mainContext.fetch(fetchRequest) as! [Albums]
    } catch {
        print(error)
    }
    delegate!.reloadAlbumsTable()
}

internal func fetchAllAlbums() {

    // Create the FetchRequest for all searches
    let allAlbums: NSFetchRequest = Albums.fetchRequest()

    do {
        items = try coreDataController.mainContext.fetch(allAlbums)
    } catch {
        print(error)
    }
}

}

【问题讨论】:

  • 你为什么定义ResultsDataModel而不是使用UITableviewDataSource
  • 请提供一个最小示例来说明您的问题。您的代码中有很多 CoreData、搜索栏等内容,这些内容可能不相关,但确实很难阅读您的代码。此外,将不胜感激正确使用降价以将所有代码包含在代码块中。
  • 我不明白你为什么编辑你的问题来更改协议的名称——关键是你根本不需要定义协议,因为它已经是 UIKit 的一部分。
  • 强制解包导致nil 崩溃的事实表明您尚未在核心数据对象的该实例上设置委托。在该对象的初始化程序中设置断点和/或向委托属性添加异议并在其中放置断点
  • 您的评论提示了一个想法,我有多个 CoreDataModel 实例,我在 viewDidLoad 上的第一个实例上设置了委托,在搜索点击时设置了第二个实例,但委托必须设置在类名而不是实例 id,重构了本身创建的 DataManager 类和 CoreDataModel 的实例,然后委托不为零,谢谢@Paulw11

标签: ios swift core-data delegation


【解决方案1】:

委托是在类名而不是任何实例标识符上分配/设置的,因此只能在具有一个实例的类上设置委托

我无法提供具体的证据,我依靠单一变化的因果来做出上述陈述。

我有多个 CoreDataModel 实例,我在 viewDidLoad 中的第一个实例上设置了委托,第二个实例在搜索点击上设置。我重构了 DataManager 类,它本身创建了 CoreDataModel 的实例。

最终的结果是委托不是 nil 并且按预期执行。 Repo'show_album_search_results' 分支

【讨论】:

  • 我在这个问题中没有看到任何证据表明这是正确的。 delegate 在您的问题代码中是一个实例属性,而不是静态或类属性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-05
  • 2018-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多