2016-03-31 22 views
5

Recentemente ho refactoring mia classe BookTableViewController da una semplice eredità da UITableViewController, in modo che ora eredita da una classe generica FetchedResultsTableViewController<TResultType, TCellType> che si eredita da UITableViewController."classe sconosciuta nel file di costruttore di interfaccia" quando si utilizza classe che eredita da una classe generica in uno storyboard

Le dichiarazioni di classe simile a questa:

class BookTableViewController: FetchedResultsTableViewController<Book, BookTableViewCell> { 

    override func viewDidLoad() { 
     // breakpoints in here do not catch! 
    } 

} 

class FetchedResultsTableViewController<TResultType, TCellType: UITableViewCell>: 
    UITableViewController, NSFetchedResultsControllerDelegate { 

    // implementation here 

} 

nello storyboard, la classe personalizzata e il modulo sono impostati, ed io può fare clic sulla freccia per passare al codice della classe BookTableViewController, suggerendo che lo storyboard è collegato correttamente alla classe. Tuttavia, quando si tenta di eseguire l'applicazione, la classe non è riconosciuto - Codice in viewDidLoad() non viene eseguito, e ricevo il seguente messaggio registrato quando si esegue il mio app:

sconosciuta classe _TtC12Reading_List23BookTableViewController nel file di Interface Builder.

Sto eseguendo XCode 7.3 (Swift 2.2). Si tratta di una limitazione con Storyboard, un bug o ho perso qualcosa?

Grazie!

UPDATE:

Dopo alcuni esperimenti, sembra essere correlato al patrimonio generici, piuttosto che l'accessibilità della classe. Con le seguenti classi definite:

import Foundation 
import UIKit 

// Both of these classes are accessible by the Storyboard 
class FirstInheritance : UITableViewController{} 
class SecondInheritance : FirstInheritance{} 

// The generic class is also accessible 
class GenericViewController<T> : UITableViewController{} 

// But this class is not accessible... 
class ConcreteViewController : GenericViewController<String>{} 

tutte le classi tranne la ConcreteViewController sono suggerite nel completamento automatico classe del Storyboard (anche se la classe generica anche non funziona, in quanto non v'è alcun modo per specificare gli argomenti di tipo nella Storyboard).

+0

E 'possibile la storyboard non conosce FetchedResultsTableViewController? –

+0

In base al tuo commento, ho notato che il campo Class nello Storyboard _adatta_completa automaticamente il 'FetchedResultsTableViewController', ma _non si_automprodotto a' BookTableViewController'. (Naturalmente, il 'FetchedResultsTableViewController' non funziona, dato che non possiamo specificare gli argomenti del tipo). Ho creato un esempio più semplice del comportamento in un aggiornamento alla domanda. Vedi anche l'esempio [qui] (https://github.com/AndrewBennet/GenericTableView). Sto iniziando a pensare che questa è una limitazione di Storyboard ... –

+0

Ah, sì, è una limitazione. Essenzialmente la stessa domanda è stata posta qui: http://stackoverflow.com/a/32899800. Potrei provare a utilizzare i protocolli con typealias ed estensioni per il comportamento predefinito. –

risposta

4

Poco tardi per il gioco, ma questa informazione potrebbe rivelarsi utile per chiunque urtare il thread.

In realtà, per quanto posso dire, ora c'è, a partire da Swift 3, una sorta di supporto scomodo per i generici negli storyboard.

Sono riuscito a scrivere una classe base generica estendendo UIViewController, quindi vincolando diverse sottoclassi di quella classe generica e usandole nello storyboard.

Il codice vero e proprio ha un layout simile a quanto specificato sotto:

class GenericParent: UIViewController { 
    // Has a few, non-generic members ... 
} 

class ActualGenericClass<T>: GenericParent { 
    // There are more generic members in the actual class 
    var adapter: Adapter<T> = Adapter() 
} 

// This class is usable in Interface Builder; 
// although auto-complete won't show it, fully writing down 
// the class name works 
class SpecificInstanceOfActualGenericClass: ActualGenericClass<Int> { 
    @IBOutlet weak var tableView: UITableView! 
} 

che funziona perfettamente, con mia grande sorpresa!

D'altra parte, il prossimo esempio non sembra funzionare:

class GenericCell<T>: UITableViewCell { 
    var node: T? = nil 
} 
class SpecificCell: GenericCell<Int> { 
    @IBOutlet weak var coolTitleLabel: UILabel! 
} 

Come prima, scrivendo esplicitamente nome della classe della cella in Interface Builder (il prototipo di dinamica della cellula) imposta la classe del la cella di visualizzazione tabella e le prese sono visibili.

In fase di runtime, tuttavia, quando si esegue l'installazione di UIViewController contenente il prototipo dinamico della cella, viene visualizzato l'avviso "Classe sconosciuta nel file del builder dell'interfaccia" e l'App si arresta in modo anomalo quando si rimuove la cella.

Così @Andew Bennet, l'affermazione che:

storyboard non supportano le classi che ereditano dalle classi generiche

non sembra essere al 100% più vero, anche se è a Almeno ERANO a posto; è la prima volta che riesco a tirarlo fuori, anche se solo parzialmente!

Mi infastidisce il fatto che alcune cose funzionino, altre no, ma è meglio di niente.

Questo è così dannatamente semplice in Java ...

2

Gli storyboard non supportano classi che ereditano da classi generiche, direttamente o indirettamente. Se si utilizza una classe che eredita da una classe generica in uno storyboard, si otterrà un errore in fase di esecuzione affermando che la classe è sconosciuta.

Un'alternativa è quella di utilizzare un protocollo con typealiases ed estensioni:

protocol MyProtocol { 
    associatedtype genericType1 
    associatedtype genericType2 

    func myFunc(argument1: genericType1, argument2: genericType2) 
} 

extension MyProtocol { 
    func defaultFunc(argument1: genericType1){ 
     // default implementation here 
    } 
} 

Il protocollo viene utilizzato come segue:

class NonGenericClass: MyProtocol { 
    typealias genericType1 = Int 
    typealias genericType2 = String 

    func myFunc(argument1: genericType1, argument2: genericType2){ 
     // specific implementation here 
    } 
} 

La classe NonGenericClass avrà tutte le funzioni nell'estensione MyProtocol (in questo caso, defaultFunc(argument1: Int)).

Il protocollo MyProtocol può anche ereditare da altri protocolli, e l'attuazione di tali funzioni possono essere definite nel prolungamento a MyProtocol. Pertanto il protocollo può rendere qualsiasi classe conforme ad esso conforme anche ad un altro protocollo, con un'implementazione standard.

Tuttavia, questo ha la limitazione che non è possibile specificare sostituzioni standard delle funzioni di classe dal protocollo.