webentwicklung-frage-antwort-db.com.de

Verwendung eines einzelnen Storyboards uiviewcontroller für mehrere Unterklassen

Angenommen, ich habe ein Storyboard, das UINavigationController als Erstansichts-Controller enthält. Sein Stammansichts-Controller ist eine Unterklasse von UITableViewController, also BasicViewController. Es hat IBAction, das mit der rechten Navigationstaste der Navigationsleiste verbunden ist

Von dort aus möchte ich das Storyboard als Vorlage für andere Ansichten verwenden, ohne zusätzliche Storyboards erstellen zu müssen. Angenommen, diese Ansichten haben genau die gleiche Oberfläche, jedoch mit dem Root-Ansichtscontroller der Klasse SpecificViewController1 und SpecificViewController2, die Unterklassen von BasicViewController sind.
Diese beiden View-Controller hätten mit Ausnahme der IBAction -Methode die gleiche Funktionalität und Schnittstelle.
Es wäre wie folgt:

@interface BasicViewController : UITableViewController

@interface SpecificViewController1 : BasicViewController

@interface SpecificViewController2 : BasicViewController

Kann ich so etwas machen?
Kann ich nur das Storyboard von BasicViewController instanziieren, aber Root-View-Controller zur Unterklasse SpecificViewController1 und SpecificViewController2?

Vielen Dank.

108
verdy

tolle frage - aber leider nur eine lahme antwort. Ich glaube nicht, dass es derzeit möglich ist, das zu tun, was Sie vorschlagen, da es in UIStoryboard keine Initialisierer gibt, mit denen der mit dem Storyboard verknüpfte Ansichtscontroller außer Kraft gesetzt werden kann, wie in den Objektdetails im Storyboard bei der Initialisierung definiert. Bei der Initialisierung werden alle UI-Elemente im Stoaryboard mit ihren Eigenschaften im View-Controller verknüpft.

Es wird standardmäßig mit dem in der Storyboard-Definition angegebenen Ansichtscontroller initialisiert.

Wenn Sie versuchen, im Storyboard erstellte Benutzeroberflächenelemente wiederzuverwenden, müssen diese weiterhin mit Eigenschaften verknüpft oder verknüpft werden, die von einem beliebigen Ansichtscontroller verwendet werden, damit der Ansichtscontroller über Ereignisse informiert werden kann.

Das Kopieren über ein Storyboard-Layout ist nicht allzu schwierig, insbesondere wenn Sie nur ein ähnliches Design für 3 Ansichten benötigen. Wenn Sie dies jedoch tun, müssen Sie sicherstellen, dass alle vorherigen Verknüpfungen gelöscht werden. Andernfalls kommt es beim Versuch zu Abstürzen um mit dem vorherigen Ansichtscontroller zu kommunizieren. Sie können diese als KVO-Fehlermeldungen in der Protokollausgabe erkennen.

Ein paar Ansätze, die Sie ergreifen könnten:

  • speichern Sie die UI-Elemente in einer UIView - in einer xib-Datei und instanziieren Sie sie aus Ihrer Basisklasse und fügen Sie sie als Unteransicht in der Hauptansicht hinzu, normalerweise self.view. Dann würden Sie einfach das Storyboard-Layout verwenden, wobei im Grunde leere Ansichts-Controller ihren Platz im Storyboard behalten, denen jedoch die richtige Ansichts-Controller-Unterklasse zugewiesen ist. Da sie von der Basis erben würden, bekämen sie diese Ansicht.

  • erstellen Sie das Layout in Code und installieren Sie es von Ihrem Basisansichts-Controller. Offensichtlich ist dieser Ansatz gegen den Zweck der Verwendung des Storyboards verstoßen, kann aber in Ihrem Fall der richtige Weg sein. Wenn Sie andere Teile der App haben, die vom Storyboard-Ansatz profitieren würden, können Sie hier und da gegebenenfalls abweichen. In diesem Fall würden Sie wie oben beschrieben nur Bank View Controller mit zugewiesener Unterklasse verwenden und die Benutzeroberfläche vom Base View Controller installieren lassen.

Es wäre schön, wenn sich Apple) eine Möglichkeit einfallen ließe, das zu tun, was Sie vorschlagen, aber die Frage, ob die Grafikelemente mit der Controller-Unterklasse vorverlinkt sind, wäre immer noch ein Problem.

ich wünsche euch ein tolles neues Jahr !! gut sein

57
CocoaEv

Der Code der Linie, die wir suchen, ist:

object_setClass(AnyObject!, AnyClass!)

In Storyboard -> UIViewController hinzufügen geben Sie ihm einen ParentVC-Klassennamen.

class ParentVC: UIViewController {

    var type: Int?

    override func awakeFromNib() {

        if type = 0 {

            object_setClass(self, ChildVC1.self)
        }
        if type = 1 {

            object_setClass(self, ChildVC2.self)
        }  
    }

    override func viewDidLoad() {   }
}

class ChildVC1: ParentVC {

    override func viewDidLoad() {
        super.viewDidLoad()

        println(type)
        // Console prints out 0
    }
}

class ChildVC2: ParentVC {

    override func viewDidLoad() {
        super.viewDidLoad()

        println(type)
        // Console prints out 1
    }
}
44
Jiří Zahálka

Wie die akzeptierte Antwort besagt, sieht es nicht so aus, als wäre es mit Storyboards möglich.

Meine Lösung besteht darin, Nibs zu verwenden - genau wie Entwickler sie vor Storyboards verwendet haben. Wenn Sie einen wiederverwendbaren, subklassifizierbaren Ansichtscontroller (oder sogar eine Ansicht) haben möchten, empfehle ich, Schreibfedern zu verwenden.

SubclassMyViewController *myViewController = [[SubclassMyViewController alloc] initWithNibName:@"MyViewController" bundle:nil]; 

Wenn Sie alle Ihre Ausgänge mit dem "Dateieigentümer" in MyViewController.xib Verbinden, geben Sie NICHT an, in welcher Klasse die Nib geladen werden soll, sondern geben nur Schlüssel-Wert-Paare an: " Diese Ansicht sollte mit diesem Instanzvariablennamen verbunden sein. " Beim Aufrufen von [SubclassMyViewController alloc] initWithNibName: Gibt der Initialisierungsprozess an, welcher Ansichtscontroller zum " Steuern " der in der Schreibfeder erstellten Ansicht verwendet wird.

13
kgaidis

Es ist möglich, dass ein Storyboard verschiedene Unterklassen eines benutzerdefinierten Ansichtscontrollers instanziiert, obwohl dies mit einer etwas unorthodoxen Technik verbunden ist: Überschreiben der alloc -Methode für den Ansichtscontroller. Beim Erstellen des benutzerdefinierten Ansichtscontrollers gibt die überschriebene Zuweisungsmethode tatsächlich das Ergebnis der Ausführung von alloc für die Unterklasse zurück.

Ich sollte die Antwort mit der Maßgabe voranstellen, dass ich, obwohl ich sie in verschiedenen Szenarien getestet habe und keine Fehler erhalten habe, nicht sicherstellen kann, dass sie komplexere Einstellungen bewältigen kann (aber ich sehe keinen Grund, warum es nicht funktionieren sollte). . Außerdem habe ich mit dieser Methode noch keine Apps eingereicht, sodass die Möglichkeit besteht, dass sie vom Überprüfungsprozess von Apple abgelehnt werden (obwohl ich auch hier keinen Grund sehe, warum dies der Fall sein sollte).

Zu Demonstrationszwecken habe ich eine Unterklasse von UIViewController mit dem Namen TestViewController, die ein UILabel-IBOutlet und eine IBAction enthält. In meinem Storyboard habe ich einen Ansichtscontroller hinzugefügt und seine Klasse in TestViewController geändert und das IBOutlet mit einem UILabel und die IBAction mit einem UIButton verknüpft. Ich präsentiere den TestViewController über einen Modal Segue, der von einem UIButton auf dem vorhergehenden viewController ausgelöst wird.

Storyboard image

Um zu steuern, welche Klasse instanziiert wird, habe ich eine statische Variable und zugehörige Klassenmethoden hinzugefügt, um die zu verwendende Unterklasse abzurufen/festzulegen (ich denke, man könnte andere Methoden anwenden, um zu bestimmen, welche Unterklasse instanziiert werden soll):

TestViewController.m:

#import "TestViewController.h"

@interface TestViewController ()
@end

@implementation TestViewController

static NSString *_classForStoryboard;

+(NSString *)classForStoryboard {
    return [_classForStoryboard copy];
}

+(void)setClassForStoryBoard:(NSString *)classString {
    if ([NSClassFromString(classString) isSubclassOfClass:[self class]]) {
        _classForStoryboard = [classString copy];
    } else {
        NSLog(@"Warning: %@ is not a subclass of %@, reverting to base class", classString, NSStringFromClass([self class]));
        _classForStoryboard = nil;
    }
}

+(instancetype)alloc {
    if (_classForStoryboard == nil) {
        return [super alloc];
    } else {
        if (NSClassFromString(_classForStoryboard) != [self class]) {
            TestViewController *subclassedVC = [NSClassFromString(_classForStoryboard) alloc];
            return subclassedVC;
        } else {
            return [super alloc];
        }
    }
}

Für meinen Test habe ich zwei Unterklassen von TestViewController: RedTestViewController und GreenTestViewController. Die Unterklassen haben jeweils zusätzliche Eigenschaften und überschreiben viewDidLoad, um die Hintergrundfarbe der Ansicht zu ändern und den Text des UILabel-IBOutlets zu aktualisieren:

RedTestViewController.m:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.view.backgroundColor = [UIColor redColor];
    self.testLabel.text = @"Set by RedTestVC";
}

GreenTestViewController.m:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor greenColor];
    self.testLabel.text = @"Set by GreenTestVC";
}

In einigen Fällen möchte ich möglicherweise TestViewController selbst instanziieren, in anderen Fällen RedTestViewController oder GreenTestViewController. In der vorhergehenden Ansichtssteuerung mache ich das zufällig wie folgt:

NSInteger vcIndex = arc4random_uniform(4);
if (vcIndex == 0) {
    NSLog(@"Chose TestVC");
    [TestViewController setClassForStoryBoard:@"TestViewController"];
} else if (vcIndex == 1) {
    NSLog(@"Chose RedVC");
    [TestViewController setClassForStoryBoard:@"RedTestViewController"];
} else if (vcIndex == 2) {
    NSLog(@"Chose BlueVC");
    [TestViewController setClassForStoryBoard:@"BlueTestViewController"];
} else {
    NSLog(@"Chose GreenVC");
    [TestViewController setClassForStoryBoard:@"GreenTestViewController"];
}

Beachten Sie, dass die Methode setClassForStoryBoard prüft, ob der angeforderte Klassenname tatsächlich eine Unterklasse von TestViewController ist, um Verwechslungen zu vermeiden. Der obige Verweis auf BlueTestViewController dient zum Testen dieser Funktionalität.

9
pbasdf

versuchen Sie dies nach instantiateViewControllerWithIdentifier.

- (void)setClass:(Class)c {
    object_setClass(self, c);
}

mögen :

SubViewController *vc = [sb instantiateViewControllerWithIdentifier:@"MainViewController"];
[vc setClass:[SubViewController class]];
6
莫振华

Obwohl es sich nicht ausschließlich um eine Unterklasse handelt, können Sie:

  1. option-Ziehen Sie den Basisklassen-Ansichtscontroller in die Dokumentstruktur, um eine Kopie zu erstellen
  2. Verschieben Sie die neue View Controller-Kopie an eine andere Stelle im Storyboard
  3. Ändern Sie die Klasse in den Unterklassenansichts-Controller im Identitätsinspektor

Hier ist ein Beispiel aus einem Block Tutorial, das ich geschrieben habe und das ViewController mit WhiskeyViewController unterordnet:

animation of the above three steps

Auf diese Weise können Sie Unterklassen von View Controller-Unterklassen im Storyboard erstellen. Sie können dann instantiateViewControllerWithIdentifier:, um bestimmte Unterklassen zu erstellen.

Dieser Ansatz ist etwas unflexibel: Spätere Änderungen im Storyboard am Basisklassen-Controller werden nicht an die Unterklasse weitergegeben. Wenn Sie eine Menge Unterklassen haben, sind Sie mit einer der anderen Lösungen möglicherweise besser dran, aber dies ist zur Not der Fall.

5
Aaron Brager

Wenn Sie nicht zu sehr auf Storyboards angewiesen sind, können Sie eine separate .xib-Datei für den Controller erstellen.

Setzen Sie den Eigentümer und die Ausgänge der entsprechenden Datei auf MainViewController und überschreiben Sie init(nibName:bundle:) im Hauptfenster VC), damit die untergeordneten Dateien auf die gleiche Schreibfeder und die gleichen Ausgänge zugreifen können.

Ihr Code sollte folgendermaßen aussehen:

class MainViewController: UIViewController {
    @IBOutlet weak var button: UIButton!

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: "MainViewController", bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        button.tintColor = .red
    }
}

Und Ihr Kind VC kann die Schreibfeder seiner Eltern wiederverwenden:

class ChildViewController: MainViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        button.tintColor = .blue
    }
}
2
nitrous

Die Methode "Objc_setclass" erstellt keine Instanz von childvc. Aber während Sie aus childvc aussteigen, wird Ihrit von childvc aufgerufen. Da für childvc kein separater Speicher zugewiesen ist, stürzt die App ab. Basecontroller hat eine Instanz, Child-VC nicht.

2
user8044830

Insbesondere basierend auf nickgzzjr und Jiří Zahálka Antworten plus Kommentar unter dem zweiten von CocoaBob habe ich eine kurze generische Methode vorbereitet, die genau das tut, was OP benötigt. Sie müssen nur den Namen des Storyboards und die ID des View Controllers-Storyboards überprüfen

class func instantiate<T: BasicViewController>(as _: T.Type) -> T? {
        let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
        guard let instance = storyboard.instantiateViewController(withIdentifier: "Identifier") as? BasicViewController else {
            return nil
        }
        object_setClass(instance, T.self)
        return instance as? T
    }

Optionale Elemente werden hinzugefügt, um ein erzwungenes Auspacken zu vermeiden, die Methode gibt jedoch korrekte Objekte zurück.

0
Adam Tucholski

Ich habe hier und da Antworten herangezogen und mir diese saubere Lösung ausgedacht.

Erstellen Sie mit dieser Funktion einen übergeordneten Ansichtscontroller.

class ParentViewController: UIViewController {


    func convert<T: ParentViewController>(to _: T.Type) {

        object_setClass(self, T.self)

    }

}

Auf diese Weise kann der Compiler sicherstellen, dass der untergeordnete Ansichtscontroller vom übergeordneten Ansichtscontroller erbt.

Wann immer Sie mit einer Unterklasse zu diesem Controller wechseln möchten, haben Sie folgende Möglichkeiten:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)

    if let parentViewController = segue.destination as? ParentViewController {
        ParentViewController.convert(to: ChildViewController.self)
    }

}

Der coole Teil ist, dass Sie einen Storyboard-Verweis auf sich selbst hinzufügen und dann den "nächsten" Child-View-Controller aufrufen können.

0
nickgzzjr

Am flexibelsten ist es wahrscheinlich, wiederverwendbare Ansichten zu verwenden.

(Erstellen Sie eine Ansicht in einer separaten XIB-Datei oder Container view und füge es zu jeder Subclass View Controller Szene im Storyboard hinzu)

0
DanSkeel