【问题标题】:A derivation of UIView cannot be casted to UIView on iOS version less than 13在低于 13 的 iOS 版本上,无法将 UIView 的派生转换为 UIView
【发布时间】:2021-11-15 15:17:50
【问题描述】:

我有一个派生自 UIView 的类,名为 ContentListView,如下所示:

import UIKit
import RxSwift
import RxRelay
import RxCocoa
import SwinjectStoryboard

class ContentListView: UIView {
    @IBInspectable var listName: String = ""
    @IBInspectable var headerHeight: CGFloat = 0
    @IBInspectable var footerHeight: CGFloat = 0

    @IBOutlet weak var tableView: UITableView!
    
    let viewDidLoad = PublishRelay<Void>()
    let viewDidAppear = PublishRelay<Void>()
    let reloadData = PublishRelay<Void>()
    let manualLoadData = PublishRelay<[ContentCellType]>()
    var initialContents: [ContentCellType]?
    private(set) lazy var selectedContent = selectedContentRelay.asSignal()
    
    private let disposeBag = DisposeBag()
    private let cellTypes = BehaviorRelay<[ContentCellType]>(value: [])
    private let didSelectIndexRelay = PublishRelay<Int>()
    private let selectedContentRelay = PublishRelay<ContentCellType>()

    private let contentNotFoundReuseId = R.reuseIdentifier.contentNotFoundErrorCell.identifier
    private let contentNotMatchReuseId = R.reuseIdentifier.contentNotMatchErrorCell.identifier
    private let myContentReuseId = R.reuseIdentifier.myContentTableViewCell.identifier
    private let associatedPracticeReuseId = R.reuseIdentifier.associatedPracticeTableViewCell.identifier
    private let associatedPracticeContentReuseId = R.reuseIdentifier.associatedPracticeContentTableViewCell.identifier
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        instantiateView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        instantiateView()
    }
    
    private func instantiateView() {
        guard let nib = R.nib.contentListView(owner: self) else { return }
        addSubview(nib, method: .fill)
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        setupTableView()
        setupViewModel()
    }
    
    private func setupTableView() {
        setupTableViewLayouts()
        registerCells()
        setupTableViewEvents()
    }
    
    private func setupViewModel() {
        let viewModel = createViewModel()
        
        viewModel.contents
            .drive(cellTypes)
            .disposed(by: self.disposeBag)
        
        viewModel.selectedContent
            .emit(to: selectedContentRelay)
            .disposed(by: disposeBag)
        
        viewDidLoad.asSignal()
            .emit(to: viewModel.viewDidLoad)
            .disposed(by: disposeBag)
        
        viewDidAppear.asSignal()
            .emit(to: viewModel.viewDidAppear)
            .disposed(by: disposeBag)
        
        reloadData.asSignal()
            .emit(to: viewModel.reloadData)
            .disposed(by: disposeBag)
        
        let loadInitialContents = Observable.just(initialContents).compactMap { $0 }
        Observable.merge(loadInitialContents,
                         manualLoadData.asObservable())
            .bind(to: viewModel.manualLoadData)
            .disposed(by: disposeBag)
        
        didSelectIndexRelay
            .bind(to: viewModel.didSelectIndex)
            .disposed(by: disposeBag)
        
    }
    
    private func createViewModel() -> ContentListViewModel {
        if let viewModel = SwinjectStoryboard.defaultContainer.resolve(ContentListViewModel.self, name: listName) {
            return viewModel
        } else {
            let viewModel = SwinjectStoryboard.defaultContainer.resolve(ContentListViewModel.self,
                                                                        name: "NoDataProvider")!
            return viewModel
        }
    }
    
    private func setupTableViewLayouts() {
        tableView.backgroundColor = R.color.grey91()
        tableView.separatorStyle = .none
    }
    
    private func registerCells() {
        tableView.register(UINib(resource: R.nib.contentNotFoundTableViewCell),
                           forCellReuseIdentifier: contentNotFoundReuseId)
        tableView.register(UINib(resource: R.nib.contentNotMatchTableViewCell),
                           forCellReuseIdentifier: contentNotMatchReuseId)
        tableView.register(UINib(resource: R.nib.myContentTableViewCell),
                           forCellReuseIdentifier: myContentReuseId)
        tableView.register(UINib(resource: R.nib.associatedPracticeTableViewCell),
                           forCellReuseIdentifier: associatedPracticeReuseId)
        tableView.register(UINib(resource: R.nib.associatedPracticeContentTableViewCell),
                           forCellReuseIdentifier: associatedPracticeContentReuseId)
    }
    
    private func setupTableViewEvents() {
        tableView.rx.setDelegate(self).disposed(by: disposeBag)
        
        cellTypes.asDriver()
            .drive(tableView.rx.items) { [weak self] tableView, _, element in
                return self?.createCell(tableView: tableView, element: element) ?? UITableViewCell()
            }
            .disposed(by: disposeBag)
        
        cellTypes.accept([.notFound])
    }
    
    private func createCell(tableView: UITableView, element: ContentCellType) -> UITableViewCell? {
        switch element {
        case .notFound: return tableView.dequeueReusableCell(withIdentifier: contentNotFoundReuseId)
        case .notMatch: return tableView.dequeueReusableCell(withIdentifier: contentNotMatchReuseId)
        case .content(data: _): return nil
        case .myContent(let data):
            let cell = tableView.dequeueReusableCell(withIdentifier: myContentReuseId) as? MyContentTableViewCell
            cell?.setup(with: data)
            return cell
        case .practice(let data):
            let cell = tableView.dequeueReusableCell(withIdentifier: associatedPracticeReuseId)
                as? AssociatedPracticeTableViewCell
            cell?.setup(with: data)
            return cell
        case .provider(let data):
            let cell = tableView.dequeueReusableCell(withIdentifier: associatedPracticeContentReuseId)
                as? AssociatedPracticeContentTableViewCell
            cell?.setup(with: data)
            return cell
        }
    }

}

extension ContentListView: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let type = cellTypes.value[indexPath.row]
        switch type {
        case .notFound, .notMatch: return 320
        case .myContent: return 440
        case .practice: return 76
        case .provider: return 412
        default: return 0
        }
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return headerHeight
    }
    
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return footerHeight
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        didSelectIndexRelay.accept(indexPath.row)
    }
}

在视图控制器中使用如下:

import UIKit
import RxSwift
import RxCocoa

class ContentsViewController: UIViewController, HideNavigationBarToggling {
    
    @IBOutlet var contentButtonViews: [ContentsButtonView]!
    @IBOutlet var contentListViews: [ContentListView]!

    private let disposeBag = DisposeBag()
    private var selectedPracticeName: String?
    private var selectedParam: MyContentViewParam?
    
    override func viewDidLoad() {
        super.viewDidLoad()
                
        hideListViews() //<<<<<<<<<<<<<< CRASH! 
        contentsButtonController.setup(with: contentButtonViews)
        contentsButtonController.activeSelectionIndex
            .drive(onNext: { [weak self] in
                self?.hideListViews()
                self?.contentListViews[$0].isHidden = false
            })
            .disposed(by: disposeBag)
        
        contentListViews.forEach {
            $0.selectedContent
                .emit(onNext: { [weak self] in self?.onSelected(with: $0) })
                .disposed(by: disposeBag)
        }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        contentListViews.forEach { $0.viewDidAppear.accept(()) }
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let providerVC = segue.destination as? AssociatedPracticeContentsViewController {
            providerVC.title = selectedPracticeName
        } else if let destinationNavigation = segue.destination as? KolibreeNavigationController,
                  let bottomVC = destinationNavigation.visibleViewController as? BottomMessageViewController {
            let messageSegue = segue as? SwiftMessagesBottomTabSegue
            messageSegue?.interactiveHide = false
            bottomVC.titleString = selectedParam?.title ?? ""
            bottomVC.setup = { [weak self] bottomMessage in
                if let pdfReader = bottomMessage as? PDFReaderMessageView,
                   let param = self?.selectedParam {
                    pdfReader.load(param: param)
                }
            }
        }
    }
    
    private func hideListViews() {
        contentListViews.forEach {
            $0.isHidden = true
        }
    }
    
    private func onSelected(with cellType: ContentCellType) {
        switch cellType {
        case .myContent(let param): openContent(for: param)
        case .practice(let param): showAssociatedPracticeContents(for: param)
        default: return
        }
    }
    
    private func openContent(for param: MyContentViewParam) {
        switch param.type {
        case .book:
            selectedParam = param
            performSegue(withIdentifier: R.segue.contentsViewController.openPdfReaderSegue.identifier, sender: nil)
        case .video, .audio:
            let avContentPlayerVC = AVContentPlayerViewController()
            present(avContentPlayerVC, animated: true) {
                avContentPlayerVC.load(param: param)
            }
        default: return
        }
    }
    
    private func showAssociatedPracticeContents(for param: AssociatedPracticeViewParam) {
        SelectedAssociatedPracticeStorageAdapter().store(param.practiceId)
        selectedPracticeName = param.practiceName
        performSegue(withIdentifier: R.segue.contentsViewController.showAssociatedPracticeContents.identifier,
                     sender: nil)
    }
}

但是当我尝试在 iOS 11 和 12 模拟器上运行它时,它崩溃了。虽然它适用于 iOS 13 和 14。但它因以下错误而崩溃:

Precondition failed: NSArray element failed to match the Swift Array Element type
Expected ContentListView but found UIView: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1001.0.82.4/swift/stdlib/public/core/ArrayBuffer.swift, line 346
2021-09-22 13:24:27.624568+0700 Kolibree[16970:513272] Precondition failed: NSArray element failed to match the Swift Array Element type
Expected ContentListView but found UIView: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1001.0.82.4/swift/stdlib/public/core/ArrayBuffer.swift, line 346

情节提要本身中的contentListViewsContentListView,所以这个错误看起来很奇怪。我该如何解决这个问题?已经好几天了,我被困在这个问题上。 :(

提前致谢。

编辑: 我尝试在情节提要中仅使用单数 ContentListView 并删除了另一个。然后我把插座改成:

@IBOutlet weak var myContentListView: ContentListView!

它产生了另一个错误:

2021-09-23 13:59:05.669493+0700 Kolibree[14267:377067] Unknown class _TtC8Kolibree15ContentListView in Interface Builder file.

实际上,当我滚动错误消息时,上面的错误消息也出现了。

也尝试这样做:

    @IBOutlet weak var myContentUIView: UIView!
    private var myContentListView: ContentListView!

    override func viewDidLoad() {
        super.viewDidLoad()

        myContentListView = myContentUIView as! ContentListView         
        ....
    } 

而且它还与其他错误产生了上述错误:

Could not cast value of type 'UIView' (0x10e6dbff8) to 'Kolibree.ContentListView' (0x106d922a0).
2021-09-23 15:29:12.151228+0700 Kolibree[15518:434665] Could not cast value of type 'UIView' (0x10e6dbff8) to 'Kolibree.ContentListView' (0x106d922a0).
Could not cast value of type 'UIView' (0x10e6dbff8) to 'Kolibree.ContentListView' (0x106d922a0).

我已经尝试了Unknown class in interface builder中的所有答案

但到目前为止没有任何效果。

【问题讨论】:

  • 如果视图是从情节提要加载的,很可能是自定义类设置不正确,或者您不小心将视图与不是自定义视图的引用集合出口相关联
  • 你能检查一下依赖关系吗,看起来 RxSwift 有不同的版本支持 Xcode 12 和 Xcode github.com/ReactiveX/RxSwift#requirements
  • @Paulw11 如果是这样的话,它在 iOS 13 和 14 上也不会工作,对吧?
  • @Md.IbrahimHassan 啊,这很有道理。我确实使用 XCode 12。谢谢。我去看看。
  • 你肯定需要修复那个“未知类”——这就是将UIView 放入你的数组的原因。确保视图自定义类中的“模块”字段为空白,并且选中了“从目标继承模块”。有时只需删除自定义类名并重新输入即可。

标签: ios swift uiview crash


【解决方案1】:

在远离该错误并决定几天再做一个专题报道后,我找到了该错误的原因。这是因为我在 UIViewControllers 和 UIViews 中使用了PublishRelaysBehaviorRelays。他们在其他任何地方都可以正常工作,只是在UIKit 类中不行。 ObservablesSignalsDriversCompleteablesSinglesMaybes 也可以在 UIViewControllers 和 UIViews 中正常工作。当我删除所有崩溃的UIViewControllersUIViews 中的所有继电器并将它们更改为使用委托时,崩溃不再出现。

【讨论】:

    猜你喜欢
    • 2020-02-05
    • 1970-01-01
    • 1970-01-01
    • 2016-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-23
    相关资源
    最近更新 更多