Подтвердить что ты не робот

Как подкласс 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()'
4b9b3361

Ответ 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")