添加编程视图而不是情节提要后出现意外的 nil
Unexpected nil after adding programmatic view instead of storyboard
我有一个以 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 { [=10=].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 { [=11=].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
}
}
'broken'协调器完全一样,只是我换了
let viewController = PostsListViewController.instance()
为
let viewController = FeedViewController()
我完全不知道为什么会这样。各个点的打印语句和断点在任何值上都没有出现 nil。
如果共享示例应用程序会更容易,请告诉我,因为我知道代码片段可能不是最明显的。
tableView.refreshControl
是 nil
。您正在尝试强制访问 nil
refreshControl。
Refreshing
属性 是 Enabled
,因为您的情节提要中的 UITableViewController
有效。在编程版本中,refreshControl
不会自动创建。
refreshControl
属性 的默认值为 nil。您需要在 UIRefreshControl
存在之前将其实例化并分配给 self.refreshControl
。
当您使用情节提要创建视图并启用它时,系统会在幕后为您处理。您将需要以编程方式自行实现。
我有一个以 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 { [=10=].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 { [=11=].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
}
}
'broken'协调器完全一样,只是我换了
let viewController = PostsListViewController.instance()
为
let viewController = FeedViewController()
我完全不知道为什么会这样。各个点的打印语句和断点在任何值上都没有出现 nil。
如果共享示例应用程序会更容易,请告诉我,因为我知道代码片段可能不是最明显的。
tableView.refreshControl
是 nil
。您正在尝试强制访问 nil
refreshControl。
Refreshing
属性 是 Enabled
,因为您的情节提要中的 UITableViewController
有效。在编程版本中,refreshControl
不会自动创建。
refreshControl
属性 的默认值为 nil。您需要在 UIRefreshControl
存在之前将其实例化并分配给 self.refreshControl
。
当您使用情节提要创建视图并启用它时,系统会在幕后为您处理。您将需要以编程方式自行实现。