126

UINavigationController初期のView Controllerとして含まれているストーリーボードがあるとしましょう。そのルート ビュー コントローラは のサブクラスでありUITableViewController、これはBasicViewControllerです。IBActionナビゲーション バーの右のナビゲーション ボタンに接続されているものがあります

。そこから、追加のストーリーボードを作成せずに、ストーリーボードを他のビューのテンプレートとして使用したいと思います。これらのビューのインターフェイスはまったく同じですが、クラスのルート ビュー コントローラーがあり、のサブクラスであるとしSpecificViewController1ます。 これらの 2 つのビュー コントローラーは、メソッドを除いて同じ機能とインターフェイスを持ちます。 次のようになります。SpecificViewController2BasicViewController
IBAction

@interface BasicViewController : UITableViewController

@interface SpecificViewController1 : BasicViewController

@interface SpecificViewController2 : BasicViewController

私はそのようなことをすることができますか?
のストーリーボードをインスタンス化するだけでBasicViewController、ルートビューコントローラーをサブクラスSpecificViewController1化できますSpecificViewController2か?

ありがとう。

4

15 に答える 15

60

素晴らしい質問ですが、残念ながら不十分な答えしかありません。UIStoryboard には、初期化時にストーリーボードのオブジェクトの詳細で定義されているように、ストーリーボードに関連付けられたビューコントローラーをオーバーライドできるイニシャライザーがないため、現在、あなたが提案したことを実行できるとは思いません。ストーリーボードのすべての UI 要素がビュー コントローラーのプロパティにリンクされるのは、初期化時です。

デフォルトでは、ストーリーボード定義で指定されたビュー コントローラーで初期化されます。

ストーリーボードで作成した UI 要素を再利用しようとしている場合でも、View Controller がそれらを使用しているプロパティにリンクまたは関連付けて、View Controller にイベントについて「伝える」ことができるようにする必要があります。

特に 3 つのビューに同様のデザインが必要な場合は、ストーリーボード レイアウトをコピーすることはそれほど大したことではありませんが、そうする場合は、以前の関連付けがすべてクリアされていることを確認する必要があります。前のView Controllerと通信します。これらは、ログ出力で KVO エラー メッセージとして認識できます。

あなたが取ることができるいくつかのアプローチ:

  • UI 要素を UIView - xib ファイルに格納し、基本クラスからインスタンス化して、メイン ビュー (通常は self.view) にサブ ビューとして追加します。次に、ストーリーボード内の場所を保持する基本的に空のビュー コントローラーを使用して、ストーリーボード レイアウトを使用しますが、正しいビュー コントローラー サブクラスが割り当てられます。ベースから継承するため、そのビューを取得します。

  • コードでレイアウトを作成し、ベース ビュー コントローラーからインストールします。明らかに、このアプローチはストーリーボードを使用する目的に反しますが、あなたの場合にはこの方法が適しているかもしれません。ストーリーボード アプローチの恩恵を受けるアプリの他の部分がある場合は、必要に応じてあちこちで逸脱してもかまいません。この場合、上記のように、サブクラスが割り当てられたバンク ビュー コントローラーを使用し、ベース ビュー コントローラーに UI をインストールさせます。

Apple があなたが提案したことを行う方法を思いついたならいいのですが、コントローラーのサブクラスに事前にリンクされたグラフィック要素を持つという問題は依然として問題になります。

よいお年をお迎えください!! よくなって

于 2013-01-01T17:51:21.120 に答える
9

ストーリーボードでカスタム ビュー コントローラーのさまざまなサブクラスをインスタンス化することは可能ですがalloc、ビュー コントローラーのメソッドをオーバーライドするという、やや非正統的な手法が必要です。カスタム ビュー コントローラが作成されると、オーバーライドされた alloc メソッドは、実際allocにはサブクラスでの実行結果を返します。

さまざまなシナリオでテストし、エラーは発生しませんでしたが、より複雑なセットアップに対応できるかどうかは保証できません (ただし、動作しない理由はわかりません)。 . また、私はこの方法を使ってアプリを提出したことがないので、Apple のレビュー プロセスによって却下される可能性はあります (ただし、そうすべき理由はわかりません)。

デモンストレーションの目的で、UILabel IBOutlet と IBAction を持つ calledUIViewControllerのサブクラスがあります。TestViewController私のストーリーボードでは、View Controller を追加し、そのクラスを に修正しTestViewController、IBOutlet を UILabel に、IBAction を UIButton に接続しました。上記の viewController の UIButton によってトリガーされるモーダル セグエ経由で TestViewController を提示します。

絵コンテ画像

どのクラスをインスタンス化するかを制御するために、静的変数と関連するクラス メソッドを追加して、使用するサブクラスを取得/設定しました (どのサブクラスをインスタンス化するかを決定する他の方法を採用できると思います)。

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];
        }
    }
}

私のテストでは、 と の 2 つのサブクラスがありTestViewControllerます。サブクラスにはそれぞれ、ビューの背景色を変更し、UILabel IBOutlet のテキストを更新するための追加のプロパティとオーバーライドがあります。RedTestViewControllerGreenTestViewControllerviewDidLoad

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";
}

TestViewController場合によっては、それ自体をインスタンス化したい場合もあればRedTestViewControllerGreenTestViewController. 前のビュー コントローラーでは、次のようにランダムにこれを行います。

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"];
}

このsetClassForStoryBoardメソッドは、要求されたクラス名が実際に TestViewController のサブクラスであることを確認して、混同を避けることに注意してください。上記の参照BlueTestViewControllerは、この機能をテストするためにあります。

于 2015-03-23T20:47:46.323 に答える
5

厳密にはサブクラスではありませんが、次のことができます。

  1. option-ドキュメント アウトラインで基本クラス ビュー コントローラーをドラッグしてコピーを作成します。
  2. 新しいView Controllerのコピーをストーリーボードの別の場所に移動します
  3. Identity Inspector でクラスをサブクラス ビュー コントローラーに変更します。

これは、私が書いたBlocチュートリアルの例で、 でサブクラス化ViewControllerされていWhiskeyViewControllerます:

上記の 3 つのステップのアニメーション

これにより、ストーリーボードでView Controllerサブクラスのサブクラスを作成できます。その後、 を使用instantiateViewControllerWithIdentifier:して特定のサブクラスを作成できます。

このアプローチは少し柔軟性がありません。後でストーリーボード内で基本クラス コントローラーに変更を加えても、サブクラスには反映されません。多くのサブクラスがある場合は、他のソリューションのいずれかを使用したほうがよい場合がありますが、これはピンチの場合に役立ちます。

于 2015-03-22T03:25:46.183 に答える
2

シンプルで明白な日常的な解決策があります。

既存のストーリーボード/コントローラーを新しいストーリーボード/コントローラー内に配置するだけです。コンテナー ビューとしての IE。

これは、View Controller の「サブクラス化」とまったく同じ概念です。

すべてがサブクラスとまったく同じように機能します。

通常、ビュー サブビューを別のビュー内に配置するのと同じように、通常、ビュー コントローラーを別のビュー コントローラー内に配置します。

他にどのようにできますか?

「サブビュー」という概念と同じくらいシンプルな、iOS の基本的な部分です。

これは簡単です...

/*

Search screen is just a modification of our List screen.

*/

import UIKit

class Search: UIViewController {
    
    var list: List!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        list = (_sb("List") as! List
        addChild(list)
        view.addSubview(list.view)
        list.view.bindEdgesToSuperview()
        list.didMove(toParent: self)
    }
}

あなたは今明らかにlistあなたがやりたいことをしなければなりません

list.mode = .blah
list.tableview.reloadData()
list.heading = 'Search!'
list.searchBar.isHidden = false

などなど

コンテナー ビューは、「サブビュー」がサブクラス化に「似ている」のと同じように、サブクラス化に「似ています」。

もちろん、「レイアウトをサブクラス化する」ことはできません - それはどういう意味ですか?

(「サブクラス化」は OO ソフトウェアに関するものであり、「レイアウト」とは関係ありません。)

ビューを再利用したい場合は、別のビュー内でサブビューするだけです。

コントローラーのレイアウトを再利用したい場合は、別のコントローラー内でそれをコンテナー ビューにするだけです。

これはiOSの最も基本的な仕組みのようなものです!!


注 - 何年もの間、別のビュー コントローラーをコンテナー ビューとして動的にロードするのは簡単なことでした。前のセクションで説明: https://stackoverflow.com/a/23403979/294884

注 - 「_sb」は、入力を節約するために使用する明らかなマクロです。

func _sb(_ s: String)->UIViewController {
    // by convention, for a screen "SomeScreen.storyboard" the
    // storyboardID must be SomeScreenID
    return UIStoryboard(name: s, bundle: nil)
       .instantiateViewController(withIdentifier: s + "ID")
}
于 2019-10-07T16:27:37.873 に答える
1

おそらく最も柔軟な方法は、再利用可能なビューを使用することです。

(別の XIB ファイルでビューを作成するかContainer view、ストーリーボードの各サブクラス ビュー コントローラー シーンに追加します)

于 2016-04-28T09:39:08.953 に答える
0

Jiří Zahálka の回答からの Cocoabob のコメントは、この解決策を得るのに役立ち、うまく機能しました。

func openChildA() {
    let storyboard = UIStoryboard(name: "Main", bundle: nil);
    let parentController = storyboard
        .instantiateViewController(withIdentifier: "ParentStoryboardID") 
        as! ParentClass;
    object_setClass(parentController, ChildA.self)
    self.present(parentController, animated: true, completion: nil);
}
于 2020-06-09T20:28:58.330 に答える