Как подкласс UITableViewController в Swift
Я хочу подклассифицировать UITableViewController и иметь возможность создавать его, вызывая инициализатор по умолчанию без аргументов.
class TestViewController: UITableViewController {
convenience init() {
self.init(style: UITableViewStyle.Plain)
}
}
Как и в Xcode 6 Beta 5, приведенный выше пример больше не работает.
Overriding declaration requires an 'override' keyword
Invalid redeclaration of 'init()'
Ответы
Ответ 1
ПРИМЕЧАНИЕ Эта ошибка исправлена в iOS 9, поэтому в этом случае все дело будет спорным. Нижеприведенное обсуждение относится только к конкретной системе и версии Swift, к которой она явно привязана.
Это явно ошибка, но также очень простое решение. Я объясню проблему, а затем даю решение. Обратите внимание, что я пишу это для Xcode 6.3.2 и Swift 1.2; Apple была на всем протяжении карты со следующего дня, когда Swift впервые вышел, поэтому другие версии будут вести себя по-другому.
Основание Бытия
Вы собираетесь создать экземпляр UITableViewController вручную (то есть, вызывая его инициализатор в коде). И вы хотите подклассифицировать UITableViewController, потому что у вас есть свойства экземпляра, которые вы хотите ему предоставить.
Проблема
Итак, вы начинаете с свойства экземпляра:
class MyTableViewController: UITableViewController {
let greeting : String
}
Это значение не имеет значения по умолчанию, поэтому вам нужно написать инициализатор:
class MyTableViewController: UITableViewController {
let greeting : String
init(greeting:String) {
self.greeting = greeting
}
}
Но это не юридический инициализатор - вы должны называть super
. Скажем, ваш вызов super
должен вызвать init(style:)
.
class MyTableViewController: UITableViewController {
let greeting : String
init(greeting:String) {
self.greeting = greeting
super.init(style: .Plain)
}
}
Но вы все еще не можете скомпилировать, потому что у вас есть требование реализовать init(coder:)
. Итак:
class MyTableViewController: UITableViewController {
let greeting : String
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(greeting:String) {
self.greeting = greeting
super.init(style: .Plain)
}
}
Теперь ваш код компилируется! Теперь вы счастливы (думаете), создаете экземпляр этого подкласса контроллера табличного представления, вызвав инициализатор, который вы написали:
let tvc = MyTableViewController(greeting:"Hello there")
Все выглядит веселым и розовым до тех пор, пока вы не запустите приложение, после чего вы получите сообщение с этим сообщением:
фатальная ошибка: использование нереализованного инициализатора init(nibName:bundle:)
Что вызывает крах и почему вы не можете его решить
Авария вызвана ошибкой в Cocoa. Неизвестный вам, init(style:)
сам вызывает init(nibName:bundle:)
. И он называет его self
. Это вы - MyTableViewController. Но MyTableViewController не имеет реализации init(nibName:bundle:)
. И не наследует init(nibName:bundle:)
, потому что вы уже предоставили назначенный инициализатор, тем самым отрезая наследование.
Единственное решение - реализовать init(nibName:bundle:)
. Но вы не можете, потому что для этой реализации вам потребуется установить свойство экземпляра greeting
- и вы не знаете, что его установить.
Простое решение
Простое решение - почти слишком простое, о чем так трудно думать - это: не подклассы UITableViewController. Почему это разумное решение? Потому что вам никогда не требовалось подклассифицировать его в первую очередь. UITableViewController - это в значительной степени бессмысленный класс; он ничего не делает для вас, что вы не можете сделать для себя.
Итак, теперь мы перепишем наш класс как подкласс UIViewController. Нам по-прежнему нужен табличный вид в качестве нашего представления, поэтому мы создадим его в loadView
, и мы также подключим его туда. Изменения отмечены как отмеченные звездочкой комментарии:
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // *
let greeting : String
weak var tableView : UITableView! // *
init(greeting:String) {
self.greeting = greeting
super.init(nibName:nil, bundle:nil) // *
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() { // *
self.view = UITableView(frame: CGRectZero, style: .Plain)
self.tableView = self.view as! UITableView
self.tableView.delegate = self
self.tableView.dataSource = self
}
}
Также вы, конечно, захотите добавить минимально необходимые методы источника данных. Теперь мы создаем экземпляр нашего класса следующим образом:
let tvc = MyViewController(greeting:"Hello there")
Наш проект компилируется и работает без заминок. Проблема решена!
Возражение - не
Вы можете возразить, что, не используя UITableViewController, мы потеряли возможность получить прототип ячейки из раскадровки. Но это не возражение, потому что у нас никогда не было этой способности в первую очередь. Помните, наша гипотеза заключается в том, что мы подклассифицируем и вызываем наш собственный инициализатор подкласса. Если бы мы получили прототип ячейки из раскадровки, раскадровка создавала бы нас, называя init(coder:)
, и проблема никогда не возникла бы в первую очередь.
Ответ 2
Xcode 6 Beta 5
Похоже, вы больше не можете объявлять инициализатор удобства без аргументов для подкласса UITableViewController. Вместо этого вам необходимо переопределить инициализатор по умолчанию.
class TestViewController: UITableViewController {
override init() {
// Overriding this method prevents other initializers from being inherited.
// The super implementation calls init:nibName:bundle:
// so we need to redeclare that initializer to prevent a runtime crash.
super.init(style: UITableViewStyle.Plain)
}
// This needs to be implemented (enforced by compiler).
required init(coder aDecoder: NSCoder!) {
// Or call super implementation
fatalError("NSCoding not supported")
}
// Need this to prevent runtime error:
// fatal error: use of unimplemented initializer 'init(nibName:bundle:)'
// for class 'TestViewController'
// I made this private since users should use the no-argument constructor.
private override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}
Ответ 3
Я сделал это так:
class TestViewController: UITableViewController {
var dsc_var: UITableViewController?
override convenience init() {
self.init(style: .Plain)
self.title = "Test"
self.clearsSelectionOnViewWillAppear = true
}
}
Создание и отображение экземпляра TestViewController
в UISplitViewController
действительно работало для меня с этим кодом.
Может быть, это плохая практика, пожалуйста, скажите мне, если это (просто началось с быстрой).
Для меня все еще существует проблема, когда есть необязательные переменные, и решение Ник Снайдер является единственным, кто работает в этой ситуации
Там всего одна проблема:
Переменные инициализируются 2 раза.
Пример:
var dsc_statistcs_ctl: StatisticsController?
var dsrc_champions: NSMutableArray
let dsc_search_controller: UISearchController
let dsrc_search_results: NSMutableArray
override init() {
dsrc_champions = dsrg_champions!
dsc_search_controller = UISearchController(searchResultsController: nil)
dsrc_search_results = NSMutableArray.array()
super.init(style: .Plain) // -> calls init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) of this class
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
// following variables were already initialized when init() was called and now initialized again
dsrc_champions = dsrg_champions!
dsc_search_controller = UISearchController(searchResultsController: nil)
dsrc_search_results = NSMutableArray.array()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
Ответ 4
Подходит для матового для отличного объяснения. Я использовал матовые и @Nick Snyder решения, однако я столкнулся с ситуацией, в которой ни одна из них не будет работать, потому что мне нужно было (1) инициализировать поля let
, (2) использовать init(style: .Grouped)
(без получения ошибка времени выполнения) и (3) использовать встроенный refreshControl
(из UITableViewController). Мое обходное решение состояло в том, чтобы ввести промежуточный класс MyTableViewController
в ObjC, а затем использовать этот класс в качестве базы для моих контроллеров представления таблиц.
MyTableViewController.h
#import <UIKit/UIKit.h>
// extend but only override 1 designated initializer
@interface MyTableViewController : UITableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
@end
MyTableViewController.m:
#import "MyTableViewController.h"
// clang will warn about missing designated initializers from
// UITableViewController without the next line. In this case
// we are intentionally doing this so we disregard the warning.
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
@implementation MyTableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style {
return [super initWithStyle:style];
}
@end
Добавьте в Project Bridging-Header.h
следующее:
#import "MyTableViewController.h"
Затем используйте в swift. Пример: "PuppyViewController.swift":
class PuppyViewController : MyTableViewController {
let _puppyTypes : [String]
init(puppyTypes : [String]) {
_puppyTypes = puppyTypes // (1) init let field (once!)
super.init(style: .Grouped) // (2) call super with style and w/o error
self.refreshControl = MyRefreshControl() // (3) setup refresh control
}
// ... rest of implementation ...
}
Ответ 5
Матовый ответ является наиболее полным, но если вы хотите использовать tableViewController в стиле .plain(скажем, по причинам, устаревшим). Тогда все, что вам нужно сделать, это позвонить
super.init(nibName: nil, bundle: nil)
вместо
super.init(style: UITableViewStyle.Plain)
или self.init(style: UITableViewStyle.Plain)
Ответ 6
Я хотел подклассифицировать UITableViewController и добавить необязательное свойство, которое требует переопределения инициализатора и решения всех проблем, описанных выше.
Использование Storyboard и segue дает вам больше опций, если вы можете работать с необязательным var, а не с необязательным let в своем подклассе UITableViewController
Вызывая executeSegueWithIdentifier и переопределяя prepareForSegue в своем представлении контроллера представления, вы можете получить экземпляр подкласса UITableViewController и установить необязательные переменные до завершения инициализации:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segueA"{
var viewController : ATableViewController = segue.destinationViewController as ATableViewController
viewController.someVariable = SomeInitializer()
}
if segue.identifier == "segueB"{
var viewController : BTableViewController = segue.destinationViewController as BTableViewController
viewController.someVariable = SomeInitializer()
}
}
Ответ 7
Я заметил подобную ошибку при использовании статических ячеек таблицы, и вы должны реализовать это:
init(coder decoder: NSCoder) {
super.init(coder: decoder)
}
если вы реализуете:
required init(coder aDecoder: NSCoder!) {
// Or call super implementation
fatalError("NSCoding not supported")
}
Я просто попал туда... Как бы ожидалось. Надеюсь, это поможет.
Ответ 8
Не уверен, что это связано с вашим вопросом, но если вы хотите инициализировать контроллер UITableView с помощью xib, Xcode 6.3 beta 4 Release Notes обеспечивает обходное решение:
- В вашем проекте Swift создайте новый пустой файл iOS Objective-C. Это приведет к тому, что лист спросит вас: "Вы хотите настроить заголовок моста Objective-C".
- Нажмите "Да", чтобы создать заголовок моста
- Внутри [YOURPROJECTNAME] -Bridging-Header.h добавьте следующий код:
@import UIKit;
@interface UITableViewController() // Extend UITableViewController to work around 19775924
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER ;
@end
Ответ 9
class ExampleViewController: UITableViewController {
private var userName: String = ""
static func create(userName: String) -> ExampleViewController {
let instance = ExampleViewController(style: UITableViewStyle.Grouped)
instance.userName = userName
return instance
}
}
let vc = ExampleViewController.create("John Doe")