I've got a class derived from UIView called ContentListView
that goes like this:
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) } }
It is used in the view controller like this:
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 HERE!!!!!! 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) } }
But when I tried to run it on iOS 11 and 12 (both simulators and actual devices), it crashed. Although it worked on iOS 13 and 14. It crashed with this error:
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
The contentListViews
in the storyboard themselves are of the type ContentListView
so the error seems weird. How do I solve this? It has been days and I'm stuck at this. :(
Thanks in advance.
Replies
1
Boosts
0
Views
1.3k
Participants
3