Objective-C で独自の UIViewController を持つサブビューを追加するにはどうすればよいでしょうか? 質問する

Objective-C で独自の UIViewController を持つサブビューを追加するにはどうすればよいでしょうか? 質問する

独自の を持つサブビューに苦労していますUIViewControllers。 にUIViewControllerはビュー (ライトピンク) と 2 つのボタンがありますtoolbar。最初のボタンが押されたときに青いビューを表示し、2 番目のボタンが押されたときに黄色いビューを表示したいと考えています。ビューを表示するだけであれば簡単なはずです。しかし、青いビューにはテーブルが含まれるため、独自のコントローラーが必要です。これが私の最初のレッスンでした。このSOの質問そこで、テーブル用のコントローラーが必要であることを知りました。

そこで、ここで少し戻って、小さなステップを踏みます。以下は、ユーティリティViewController(メイン ビュー コントローラー) と他の 2 つのコントローラー (青と黄色) を使用した簡単な開始点の図です。ユーティリティViewController(メイン ビュー) が最初に表示されるときに、ピンク色のビューが配置されている場所に青 (デフォルト) ビューが表示されることを想像してください。ユーザーは 2 つのボタンをクリックして前後に移動できますが、ピンク色のビューは決して表示されません。私は、青のビューをピンク色のビューがある場所に移動し、黄色のビューをピンク色のビューがある場所に移動させたいだけです。これで意味が通じると思います。

シンプルなストーリーボード画像

を使用しようとしていますaddChildViewController。私が見たところ、これを行うには 2 つの方法があります。 のコンテナー ビューを使用するか、storyboardプログラムaddChildViewControllerで実行します。プログラムで実行します。 やタブ バーは使用したくありません。NavigationControllerコントローラーを追加して、関連付けられたボタンが押されたときに正しいビューをピンクのビューに押し込むだけです。

以下は、私がこれまでに作成したコードです。私がやりたいのは、ピンクのビューがある場所に青いビューを表示することだけです。私が見たところ、addChildViewControlleraddSubView だけで済むはずです。このコードではそれができません。混乱が私を圧倒しています。誰か、ピンクのビューがある場所に青いビューを表示できるように手伝ってくれませんか?

このコードは、viewDidLoad で青いビューを表示する以外の目的はありません。

IDユーティリティビューコントローラ.h

#import <UIKit/UIKit.h>

@interface IDUtilityViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *utilityView;
@end

IDユーティリティビューコントローラ.m

#import "IDUtilityViewController.h"
#import "IDAboutViewController.h"

@interface IDUtilityViewController ()
@property (nonatomic, strong) IDAboutViewController *aboutVC;
@end

@implementation IDUtilityViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.aboutVC = [[IDAboutViewController alloc]initWithNibName:@"AboutVC" bundle:nil];
    [self addChildViewController:self.aboutVC];
    [self.aboutVC didMoveToParentViewController:self];
    [self.utilityView addSubview:self.aboutVC.aboutView];
}

@end

- - - - - - - - - - - - - 編集 - - - - - - - - - - - - - - -

self.aboutVC.aboutView は nil です。しかし、それを に接続しましたstoryboard。それでもインスタンス化する必要がありますか?

ここに画像の説明を入力してください

ベストアンサー1

この投稿は、現代の iOS の初期の頃に書かれたものです。最新の情報と現在の Swift 構文に基づいて更新されています。

今日のiOS「すべてがコンテナビューです」これは、今日のアプリを作成する基本的な方法です。

アプリは非常にシンプルで、画面が 1 つしかない場合もあります。しかし、その場合でも、画面上の各「もの」はコンテナ ビューです。

とても簡単です...

バージョンノート

2020 年。最近では、別のストーリーボードからコンテナ ビューをロードするだけなので、非常に簡単です。この投稿の下部で説明されています。コンテナ ビューを初めて使用する場合は、まず「クラシック スタイル」(「同じストーリーボード」) のコンテナ チュートリアルに慣れておくとよいでしょう。

2021。構文を更新しました。SO の新しい「###」のきれいな見出しを使用しました。コードからの読み込みについての詳細。


(A) コンテナ ビューをシーンにドラッグします...

コンテナ ビューをシーン ビューにドラッグします (UIButton などの任意の要素をドラッグする場合と同じです)。

コンテナビューはこの画像の茶色のものです。内部あなたのシーンビュー。

ここに画像の説明を入力してください

コンテナビューをシーンビューにドラッグすると、Xcodeは自動的に二つのこと:

  1. コンテナビューが表示されます内部シーンビュー、そして、

  2. あなたは真新しいUIViewControllerものを手に入れますストーリーボードの白い部分のどこかにただ置いてある

二人は接続された「フリーメーソンのシンボル」については以下で説明します。


(B) 新しいビューコントローラをクリックします。(白い領域のどこかにXcodeが作成した新しいものが表示されます。あなたのシーンの中にあるものじゃない.) ...そして、クラスを変更します!

本当にそれだけです。

完了です。


同じことを視覚的に説明すると次のようになります。

注意してくださいコンテナビュー(A)

注意してくださいコントローラ(B)

コンテナビューと関連するビューコントローラを表示します

B をクリックします。(A ではなく B です)

右上のインスペクタに移動します。「UIViewController」と表示されていることに注意してください。

ここに画像の説明を入力してください

独自のカスタム クラス (UIViewController) に変更します。

ここに画像の説明を入力してください

Snapつまり、というSwift クラスがありますUIViewController

ここに画像の説明を入力してください

そこで、インスペクターで「UIViewController」と表示されているところに「Snap」と入力しました。

(通常どおり、Xcode は「Snap...」と入力し始めると「Snap」を自動補完します。)

これで完了です。


コンテナ ビューをテーブル ビューなどに変更する方法。

したがって、コンテナ ビューを追加するためにクリックすると、Apple はストーリーボード上にリンクされたビュー コントローラを自動的に提供します。

UIViewController現在(2019年)はデフォルトでこれになっています。

それはばかげています。どのタイプが必要かを尋ねるべきです。たとえば、多くの場合、テーブル ビューが必要になります。

方法は次のとおりですそれを変更何か違うものに:

執筆時点では、Xcode はUIViewControllerデフォルトで を提供します。代わりに が必要な場合は、次のようにしますUICollectionViewController

(i) コンテナ ビューをシーンにドラッグします。Xcode がデフォルトで提供するストーリーボード上の UIViewController を確認します。

UICollectionViewController(ii)ストーリーボードのメインの白い領域の任意の場所に新しいものをドラッグします。

(iii) シーン内のコンテナ ビューをクリックします。接続インスペクタをクリックします。「トリガーされたセグエ」が 1 つあることに注意してください。マウスオーバー「トリガーされたセグエ」とXcodeに注目してくださいハイライト不要な UIViewController をすべて削除します。

(iv) 「x」をクリックすると実際に消去それがセグエをトリガーしました。

(動詞)引っ張るトリガーされたセグエから(viewDidLoadが唯一の選択肢です)ストーリーボードをドラッグして新しいUICollectionViewControllerに移動します。手を離すとポップアップが表示されます。しなければならない選択する埋め込み

(vi) 単純に消去不要な UIViewController をすべて削除します。これで完了です。

短縮版:

  • 不要な UIViewController を削除します。

  • UICollectionViewControllerストーリーボード上の任意の場所に新しいものを配置します。

  • コントロールドラッグからコンテナビューの接続 - トリガー Segue - viewDidLoad を新しいコントローラーに送信します。

  • ポップアップでは必ず「埋め込み」を選択してください。

とても簡単です。


テキスト識別子を入力してください...

これらのいずれかを入手できます「四角の中に四角」フリーメーソンのシンボル: コンテナ ビューとビュー コントローラを接続する「曲がった線」上にあります。

「フリーメーソンのシンボル」というのはセグエ

ここに画像の説明を入力してください

クリックしてセグエを選択しますの上「フリーメーソンのシンボル」のこと。

右を見てください。

あなたしなければならない入力してくださいテキスト識別子セグエのために。

名前はあなたが決めてください。任意のテキスト文字列を指定できます。多くの場合、「segueClassName」が適切な選択肢です。

このパターンに従うと、すべてのセグエは segueClockView、seguePersonSelector、segueSnap、segueCards などの名前になります。

次に、そのテキスト識別子はどこで使用しますか?


子コントローラーに接続する方法...

次に、シーン全体の ViewController でコードで次の操作を実行します。

シーンに 3 つのコンテナ ビューがあるとします。各コンテナ ビューには、「スナップ」、「クロック」、「その他」など、異なるコントローラが保持されます。

最新の構文

var snap:Snap?
var clock:Clock?
var other:Other?

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if (segue.identifier == "segueSnap")
            { snap = (segue.destination as! Snap) }
    if (segue.identifier == "segueClock")
            { clock = (segue.destination as! Clock) }
    if (segue.identifier == "segueOther")
            { other = (segue.destination as! Other) }
}

とても簡単です。prepareForSegue呼び出しを使用して、コントローラーを参照する変数を接続します。


親まで「反対方向」に接続する方法...

コンテナ ビューに配置したコントローラー (例では「Snap」) 内にいるとします。

上位の「ボス」ビュー コントローラ (例では「Dash」) に到達するのは混乱を招く可能性があります。幸いなことに、これは次のように簡単です。

// Dash is the overall scene.
// Here we are in Snap. Snap is one of the container views inside Dash.

class Snap {

var myBoss:Dash?    
override func viewDidAppear(_ animated: Bool) { // MUST be viewDidAppear
    super.viewDidAppear(animated)
    myBoss = parent as? Dash
}

致命的:以降のみ動作しますviewDidAppear。 では動作しませんviewDidLoad

完了です。


重要:のみコンテナ ビューで機能します。

ヒント: コンテナ ビューに対してのみ機能することを忘れないでください。

最近ではストーリーボード識別子を使用して、画面に新しいビューをポップするだけでよくなっています (Android 開発のように)。ユーザーが何かを編集したいとします...

    // let's just pop a view on the screen.
    // this has nothing to do with container views
    //
    let e = ...instantiateViewController(withIdentifier: "Edit") as! Edit
    e.modalPresentationStyle = .overCurrentContext
    self.present(e, animated: false, completion: nil)

コンテナビューを使用する場合、保証されていますDash が Snap の親ビュー コントローラーになります。

しかしそれは必ずしもそうではないinstanceiateViewController を使用する場合。

非常に紛らわしいことに、iOSでは親ビューコントローラは関係ないそれをインスタンス化したクラスに。(かもしれない同じになることもありますが、通常は同じではありません。self.parentパターンはのみコンテナ ビュー用。

(instantiateViewController パターンで同様の結果を得るには、プロトコルとデリゲートを使用する必要があります。デリゲートは弱いリンクになることに注意してください。)

ただし、最近では別のストーリーボードからコンテナ ビューを動的にロードするのは非常に簡単であることに注意してください (以下の最後のセクションを参照)。多くの場合、これが最善の方法です。


prepareForSegue の名前が不適切です...

「prepareForSegue」は、本当に悪い名前です!

「prepareForSegue」は、コンテナ ビューの読み込みと、シーン間のセグエという 2 つの目的で使用されます。

しかし実際には、シーン間の切り替えはほとんどありません。一方、ほとんどすべてのアプリには、当然のことながら、多数のコンテナー ビューがあります。

「prepareForSegue」が「loadingContainerView」のような名前であれば、より意味が通るでしょう。


複数の...

よくある状況としては、画面上に小さな領域があり、そこに複数の異なるビュー コントローラーの 1 つを表示する場合です。たとえば、4 つのウィジェットの 1 つなどです。

これを行う最も簡単な方法は、4つの異なるコンテナビュー全員が中に座って同じ同一エリアコードでは、4 つすべてを非表示にして、表示したい 1 つをオンにするだけです。

簡単。


「コードからの」コンテナ ビュー...

... ストーリーボードをコンテナー ビューに動的に読み込みます。

2019+ 構文

ストーリーボード ファイル「Map.storyboard」があり、ストーリーボード ID が「MapID」で、ストーリーボードがMapクラスのビュー コントローラーであるとします。

let map = UIStoryboard(name: "Map", bundle: nil)
           .instantiateViewController(withIdentifier: "MapID")
           as! Map

メインシーンに通常の UIView を配置します。

@IBOutlet var dynamicContainerView: UIView!

アップルの説明ここ動的コンテナビューを追加するために必要な4つのこと

addChild(map)
map.view.frame = dynamicContainerView.bounds
dynamicContainerView.addSubview(map.view)
map.didMove(toParent: self)

(この順番で)

コンテナ ビューを削除するには、次のようにします。

map.willMove(toParent: nil)
map.view.removeFromSuperview()
map.removeFromParent()

(順番もこの通りです)以上です。

ただし、この例では、dynamicContainerViewビューは固定されているだけであることに注意してください。ビューは変更もサイズ変更もされません。これは、アプリが回転したり、その他の操作を行ったりしない場合にのみ機能します。通常、map.view を dynamicContainerView 内に保持してサイズを変更できるようにするには、通常の 4 つの制約を追加する必要があります。実際、iOS アプリに必要な「世界で最も便利な拡張機能」がここにあります。

extension UIView {
    
    // it's basically impossible to make an iOS app without this!
    
    func bindEdgesToSuperview() {
        
        guard let s = superview else {
            preconditionFailure("`superview` nil in bindEdgesToSuperview")
        }
        
        translatesAutoresizingMaskIntoConstraints = false
        leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: s.topAnchor).isActive = true
        bottomAnchor.constraint(equalTo: s.bottomAnchor).isActive = true
    }
}

したがって、実際のアプリでは上記のコードは次のようになります。

addChild(map)
dynamicContainerView.addSubview(map.view)
map.view.bindEdgesToSuperview()
map.didMove(toParent: self)

.addSubviewAndBindEdgesToSuperview()(コード行を避けるために拡張機能を作成する人もいます!)

注文は

  • 子供を追加する
  • 実際のビューを追加する
  • didMoveを呼び出す

それらのうちの1つを削除しますか?

mapホルダーに動的に追加しましたが、これを削除したいとします。正しい唯一の順序は次のとおりです。

map.willMove(toParent: nil)
map.view.removeFromSuperview()
map.removeFromParent()

多くの場合、ホルダー ビューがあり、さまざまなコントローラーを入れ替えたい場合があります。そのため、次のようになります。

var current: UIViewController? = nil
private func _install(_ newOne: UIViewController) {
    if let c = current {
        c.willMove(toParent: nil)
        c.view.removeFromSuperview()
        c.removeFromParent()
    }
    current = newOne
    addChild(current!)
    holder.addSubview(current!.view)
    current!.view.bindEdgesToSuperview()
    current!.didMove(toParent: self)
}

おすすめ記事