无法将 UIView 的派生转换为 iOS 版本低于 13 的 UIView
A derivation of UIView cannot be casted to UIView on iOS version less than 13
我有一个派生自 UIView 的 class,名为 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 { [=11=] }
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[[=12=]].isHidden = false
})
.disposed(by: disposeBag)
contentListViews.forEach {
[=12=].selectedContent
.emit(onNext: { [weak self] in self?.onSelected(with: [=12=]) })
.disposed(by: disposeBag)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
contentListViews.forEach { [=12=].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 {
[=12=].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
情节提要中的 contentListViews
本身是 ContentListView
所以错误看起来很奇怪。我该如何解决这个问题?已经好几天了,我一直坚持这一点。 :(
提前致谢。
编辑:
我试过在情节提要中只使用一个单数 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
中的所有答案
但到目前为止没有任何效果。
在与错误保持距离并决定用几天时间再做一个专题报道后,我找到了错误的原因。这是因为我在 UIViewControllers 和 UIViews 中使用了 PublishRelays
和 BehaviorRelays
。他们在其他任何地方都工作得很好,只是在 UIKit
类中不行。 Observables
、Signals
、Drivers
、Completeables
、Singles
和 Maybes
也可以在 UIViewController 和 UIView 中正常工作。当我删除所有崩溃的 UIViewControllers
和 UIViews
中的所有中继并将它们更改为使用代表时,崩溃不再出现。
我有一个派生自 UIView 的 class,名为 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 { [=11=] }
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[[=12=]].isHidden = false
})
.disposed(by: disposeBag)
contentListViews.forEach {
[=12=].selectedContent
.emit(onNext: { [weak self] in self?.onSelected(with: [=12=]) })
.disposed(by: disposeBag)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
contentListViews.forEach { [=12=].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 {
[=12=].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
情节提要中的 contentListViews
本身是 ContentListView
所以错误看起来很奇怪。我该如何解决这个问题?已经好几天了,我一直坚持这一点。 :(
提前致谢。
编辑:
我试过在情节提要中只使用一个单数 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
中的所有答案但到目前为止没有任何效果。
在与错误保持距离并决定用几天时间再做一个专题报道后,我找到了错误的原因。这是因为我在 UIViewControllers 和 UIViews 中使用了 PublishRelays
和 BehaviorRelays
。他们在其他任何地方都工作得很好,只是在 UIKit
类中不行。 Observables
、Signals
、Drivers
、Completeables
、Singles
和 Maybes
也可以在 UIViewController 和 UIView 中正常工作。当我删除所有崩溃的 UIViewControllers
和 UIViews
中的所有中继并将它们更改为使用代表时,崩溃不再出现。