У меня есть приложение, написанное по шаблону MVVM-C с использованием RxSwift
После добавления нового представления программно приложение аварийно завершает работу с
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
ошибка. Я в полной растерянности, реализация почти такая же, за исключением того факта, что один контроллер представления является раскадровкой, а другой - нет.
Это мой новый ViewController
import UIKit
import RxSwift
import RxCocoa
final class FeedViewController: TableViewController, ViewModelAttaching {
var viewModel: Attachable<FeedViewModel>!
var bindings: FeedViewModel.Bindings {
let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
.mapToVoid()
.asDriverOnErrorJustComplete()
let refresh = tableView.refreshControl!.rx
.controlEvent(.valueChanged)
.asDriver()
return FeedViewModel.Bindings(
fetchTrigger: Driver.merge(viewWillAppear, refresh),
selection: tableView.rx.itemSelected.asDriver()
)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func bind(viewModel: FeedViewModel) -> FeedViewModel {
viewModel.posts
.drive(tableView.rx.items(cellIdentifier: FeedTableViewCell.reuseID, cellType: FeedTableViewCell.self)) { _, viewModel, cell in
cell.bind(to: viewModel)
}
.disposed(by: disposeBag)
viewModel.fetching
.drive(tableView.refreshControl!.rx.isRefreshing)
.disposed(by: disposeBag)
viewModel.errors
.delay(0.1)
.map { $0.localizedDescription }
.drive(errorAlert)
.disposed(by: disposeBag)
return viewModel
}
}
Это существующий, который работает, но использует раскадровки.
final class PostsListViewController: TableViewController, ViewModelAttaching {
var viewModel: Attachable<PostsListViewModel>!
var bindings: PostsListViewModel.Bindings {
let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
.mapToVoid()
.asDriverOnErrorJustComplete()
let refresh = tableView.refreshControl!.rx
.controlEvent(.valueChanged)
.asDriver()
return PostsListViewModel.Bindings(
fetchTrigger: Driver.merge(viewWillAppear, refresh),
selection: tableView.rx.itemSelected.asDriver()
)
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
// MARK: - View Methods
private func setupView() {
title = "Posts"
}
func bind(viewModel: PostsListViewModel) -> PostsListViewModel {
viewModel.posts
.drive(tableView.rx.items(cellIdentifier: PostTableViewCell.reuseID, cellType: PostTableViewCell.self)) { _, viewModel, cell in
cell.bind(to: viewModel)
}
.disposed(by: disposeBag)
viewModel.fetching
.drive(tableView.refreshControl!.rx.isRefreshing)
.disposed(by: disposeBag)
viewModel.errors
.delay(0.1)
.map { $0.localizedDescription }
.drive(errorAlert)
.disposed(by: disposeBag)
return viewModel
}
}
Они в основном точно такие же. Исключение выдается в строке let refresh = tableView.refreshControl!.rx.
Рабочий координатор, использующий раскадровку,
import RxSwift
class PostsCoordinator: BaseCoordinator<Void> {
typealias Dependencies = HasPostService
private let navigationController: UINavigationController
private let dependencies: Dependencies
init(navigationController: UINavigationController, dependencies: Dependencies) {
self.navigationController = navigationController
self.dependencies = dependencies
}
override func start() -> Observable<Void> {
let viewController = PostsListViewController.instance()
navigationController.viewControllers = [viewController]
let avm: Attachable<PostsListViewModel> = .detached(dependencies)
let viewModel = viewController.attach(wrapper: avm)
viewModel.selectedPost
.drive(onNext: { [weak self] selection in
self?.showDetailView(with: selection)
})
.disposed(by: viewController.disposeBag)
// View will never be dismissed
return Observable.never()
}
private func showDetailView(with post: Post) {
let viewController = PostDetailViewController.instance()
viewController.viewModel = PostDetailViewModel(post: post)
navigationController.showDetailViewController(viewController, sender: nil)
}
}
У меня есть расширение, позволяющее мне также создавать его экземпляры.
protocol Reusable {
static var reuseID: String { get }
}
extension Reusable {
static var reuseID: String {
return String(describing: self)
}
}
// MARK: - View Controller
extension UIViewController: Reusable {
class func instance() -> Self {
let storyboard = UIStoryboard(name: reuseID, bundle: nil)
return storyboard.instantiateViewController()
}
}
extension UIStoryboard {
func instantiateViewController<T: UIViewController>() -> T {
guard let viewController = self.instantiateViewController(withIdentifier: T.reuseID) as? T else {
fatalError("Unable to instantiate view controller: \(T.self)")
}
return viewController
}
}
«Сломанный» координатор точно такой же, за исключением того, что я поменял местами
let viewController = PostsListViewController.instance()
для
let viewController = FeedViewController()
Я в полной растерянности, почему это бросает. Операторы печати и точки останова в разных точках не давали нуля ни для каких значений.
Пожалуйста, дайте мне знать, если будет проще поделиться примером приложения, так как я понимаю, что фрагменты кода могут быть не самыми очевидными.





tableView.refreshControl есть nil. Вы пытаетесь принудительно получить доступ к nil refreshControl.
Свойство Refreshing — это Enabled для UITableViewController в вашей раскадровке, которая работает. В программной версии refreshControl не создается автоматически.
@blukkublukku Обновленный ответ. В раскадровке вполне вероятно, что свойство Refreshing UITableViewController установлено в Enabled. Таким образом, элемент управления создается автоматически.
Значение по умолчанию свойства refreshControl равно нулю. Вам нужно создать экземпляр и присвоить UIRefreshControlself.refreshControl до того, как он будет создан.
Когда вы создаете свое представление с помощью раскадровки и включаете его, об этом позаботятся за вас за кулисами. Программно вам потребуется реализовать это самостоятельно.
Спасибо, что нашли время, чтобы прокомментировать, я понимаю, что это так, я не понимаю, почему переход от раскадровки к программному
UITableViewControllerприведет к нулю