View Controller間でのデータの受け渡し


1372

私はiOSとObjective-C、およびMVCパラダイム全体に不慣れで、次の問題に悩まされています。

データ入力フォームとして機能するビューがあり、ユーザーに複数の製品を選択するオプションを提供したいと考えています。製品は別のビューにと表示され、UITableViewController複数の選択が可能になっています。

私の質問は、あるビューから別のビューにデータを転送するにはどうすればよいですか?UITableView配列の選択を保持しますが、それを以前のデータ入力フォームビューに戻し、フォームの送信時に他のデータと一緒にコアデータに保存できるようにするにはどうすればよいですか?

サーフィンをして、一部の人々がアプリのデリゲートで配列を宣言しているのを見ました。私はシングルトンについて何かを読みましたが、これらが何であるかを理解しておらず、データモデルの作成について何かを読みました。

これを実行する正しい方法は何ですか?どうすればいいですか?

回答:


1684

この質問はここでstackoverflowで非常に人気があるようですので、私のようなiOSの世界で始めている人々を助けるためにもっと良い答えを出そうと思いました。

この答えが人々に理解できるほど明確であり、私が何も見逃していないことを願っています。

データを転送する

別のビューコントローラーからビューコントローラーにデータを転送する。ナビゲーションスタックにプッシュしている可能性があるビューコントローラーから別のビューコントローラーにオブジェクト/値を渡す場合は、このメソッドを使用します。

この例ではViewControllerAViewControllerB

合格するにはBOOLからの値ViewControllerAViewControllerB、我々は次のことを行うだろうし。

  1. ViewControllerB.hプロパティを作成するBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. ViewControllerAあなたはそれを伝える必要がありますについてViewControllerBその使用

    #import "ViewControllerB.h"

    次に、ビューをロードする場所。didSelectRowAtIndexまたは、ナビスタックにプッシュする前にIBActionプロパティを設定する必要があるものViewControllerB

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.isSomethingEnabled = YES;
    [self pushViewController:viewControllerB animated:YES];

    これはvalueに設定isSomethingEnabledViewControllerBBOOLますYES

セグエを使用してデータを転送する

ストーリーボードを使用している場合は、セグエを使用している可能性が高く、データを転送するためにこの手順が必要になります。これは上記に似ていますが、ビューコントローラーをプッシュする前にデータを渡す代わりに、

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

だから、渡すBOOLからViewControllerAViewControllerB私たちは、次の操作を行います。

  1. ViewControllerB.hプロパティを作成するBOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. ViewControllerAあなたはそれを伝える必要がありますについてViewControllerBその使用

    #import "ViewControllerB.h"
  3. ストーリーボードにセグエfrom ViewControllerAを作成ViewControllerBし、識別子を付けます。この例では、"showDetailSegue"

  4. 次に、ViewControllerAセグエが実行されたときに呼び出されるメソッドにメソッドを追加する必要があります。これは、どのセグエが呼び出されたかを検出して何かを行う必要があるためです。この例では、チェックを"showDetailSegue"行い、それが実行された場合、BOOL値をViewControllerB

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
            controller.isSomethingEnabled = YES;
        }
    }

    ナビゲーションコントローラーにビューを埋め込んでいる場合は、上記のメソッドを次のように少し変更する必要があります。

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
            ViewControllerB *controller = (ViewControllerB *)navController.topViewController;
            controller.isSomethingEnabled = YES;
        }
    }

    これはvalueに設定isSomethingEnabledViewControllerBBOOLますYES

データの受け渡し

以下からのデータのバックを渡すにViewControllerBViewControllerA、あなたが使用する必要があるプロトコルおよび代表者またはブロックを、後者はコールバックの疎結合メカニズムとして使用することができます。

これを行うにはViewControllerA、のデリゲートを作成しViewControllerBます。これによりViewControllerB、メッセージを送り返してViewControllerA、データを送り返すことができます。

ViewControllerAデリゲートになるには、が指定する必要があるのプロトコルにViewControllerB準拠する必要がありViewControllerBます。これにより、ViewControllerAどのメソッドを実装する必要があるかがわかります。

  1. ではViewControllerB.h、の下#import、ただし上で@interfaceプロトコルを指定します。

    @class ViewControllerB;
    
    @protocol ViewControllerBDelegate <NSObject>
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
  2. 次に、まだプロパティViewControllerB.hを設定してdelegate合成する必要がありますViewControllerB.m

    @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
  3. では、ビューコントローラをポップするときにViewControllerBメッセージを呼び出しますdelegate

    NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
  4. これで終わりですViewControllerB。次に、インポートしてプロトコルに準拠するようにViewControllerA.h指示ViewControllerAViewControllerBます。

    #import "ViewControllerB.h"
    
    @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
  5. ではViewControllerA.m私たちのプロトコルから、次のメソッドを実装

    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
        NSLog(@"This was returned from ViewControllerB %@",item);
    }
  6. viewControllerBナビゲーションスタックにプッシュする前に、それがそのデリゲートであるViewControllerBことを伝える必要 ViewControllerAがあります。そうでない場合、エラーが発生します。

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.delegate = self
    [[self navigationController] pushViewController:viewControllerB animated:YES];

参考文献

  1. 他のビューコントローラと通信するために委任を使用中にプログラミングガイドビューコントローラ
  2. デリゲートパターン

NSNotificationセンター データを渡すもう1つの方法です。

// add observer in controller(s) where you want to receive data
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDeepLinking:) name:@"handleDeepLinking" object:nil];

-(void) handleDeepLinking:(NSNotification *) notification {
    id someObject = notification.object // some custom object that was passed with notification fire.
}

// post notification
id someObject;
[NSNotificationCenter.defaultCenter postNotificationName:@"handleDeepLinking" object:someObject];

あるクラスから別のクラスにデータを返す(クラスは、任意のコントローラー、ネットワーク/セッションマネージャー、UIViewサブクラス、またはその他のクラスにすることができます)

ブロックは無名関数です。

この例では、コントローラーBからコントローラーAにデータを渡します。

ブロックを定義する

@property void(^selectedVoucherBlock)(NSString *); // in ContollerA.h

値が必要な場所にブロックハンドラー(リスナー)を追加します(たとえば、ControllerAでAPI応答が必要か、AにContorllerBデータが必要です)。

// in ContollerA.m

- (void)viewDidLoad {
    [super viewDidLoad];
    __unsafe_unretained typeof(self) weakSelf = self;
    self.selectedVoucherBlock = ^(NSString *voucher) {
        weakSelf->someLabel.text = voucher;
    };
}

コントローラBに移動

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
ControllerB *vc = [storyboard instantiateViewControllerWithIdentifier:@"ControllerB"];
vc.sourceVC = self;
    [self.navigationController pushViewController:vc animated:NO];

ファイアーブロック

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath: 
(NSIndexPath *)indexPath {
    NSString *voucher = vouchersArray[indexPath.row];
    if (sourceVC.selectVoucherBlock) {
        sourceVC.selectVoucherBlock(voucher);
    }
    [self.navigationController popToViewController:sourceVC animated:YES];
}

ブロックの別の実用例


24
我々はまた、配置する必要があります@class ViewControllerB;@protocol定義の上に?これがないと、ViewControllerBの「Expected type」エラーが次のように表示され - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; ます。@protocol宣言内
alan-p

4
これはうまくいきます。alan-pが言うように、@ class ViewControllerBを書くことを忘れないでください。それ以外の場合は、「予想されるタイプ」エラーが表示されます。
Andrew Davis

6
渡すためにデリゲートは必要ありません。単にunwindを使用してください。
マルハル2013

4
「viewControllerB.delegate = self;」と入力すると ViewControllerBでエラーが発生します。互換性のないタイプ「ViewControllerB * const __strong」から「id <ViewControllerBDelegate>」に割り当てると、私は何が間違っているのかわかりません。誰か助けてもらえますか?さらに、変更する必要がありました:initWithNib-> initWithNibName。
uplearnedu.com 2014年

4
使用しNavigationControllerている場合は、[self.navigationController pushViewController:viewController animated:YES];代わりに使用する必要があります [self pushViewController:viewControllerB animated:YES];
Nazir

192

迅速

こことStackOverflowの周りにはたくさんの説明がありますが、初心者が基本的な作業をしようとしているだけの場合は、このYouTubeチュートリアルを見てみてください(それが最終的にそれを行う方法を理解するのに役立ちました)。

次のView Controllerにデータを転送する

以下は、ビデオに基づく例です。アイデアは、First View ControllerのテキストフィールドからSecond View Controllerのラベルに文字列を渡すことです。

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

Interface Builderでストーリーボードレイアウトを作成します。セグエを作成するにはControl、ボタンをクリックして、Second View Controllerまでドラッグします。

First View Controller

First View Controllerのコードは

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        // get a reference to the second view controller
        let secondViewController = segue.destination as! SecondViewController

        // set a variable in the second view controller with the String to pass
        secondViewController.receivedString = textField.text!
    }

}

セカンドビューコントローラー

そしてSecond View Controllerのコードは

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    // This variable will hold the data being passed from the First View Controller
    var receivedString = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // Used the text from the First View Controller to set the label
        label.text = receivedString
    }

}

忘れないで

  • とのアウトレットを接続UITextFieldUILabelます。
  • 最初と2番目のView ControllerをIBの適切なSwiftファイルに設定します。

前のView Controllerにデータを戻す

2番目のView Controllerから最初のView Controllerにデータを戻すには、プロトコルとデリゲートを使用します。このビデオはそのプロセスの非常に明確なウォークです:

以下は、ビデオに基づいた例です(いくつか変更を加えています)。

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

Interface Builderでストーリーボードレイアウトを作成します。この場合も、セグエを作成するにはControl、ボタンからセカンドビューコントローラーにドラッグするだけです。セグエ識別子をに設定しshowSecondViewControllerます。また、次のコードの名前を使用して、アウトレットとアクションを接続することを忘れないでください。

First View Controller

First View Controllerのコードは

import UIKit

class FirstViewController: UIViewController, DataEnteredDelegate {

    @IBOutlet weak var label: UILabel!

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showSecondViewController" {
            let secondViewController = segue.destination as! SecondViewController
            secondViewController.delegate = self
        }
    }

    func userDidEnterInformation(info: String) {
        label.text = info
    }
}

カスタムDataEnteredDelegateプロトコルの使用に注意してください。

セカンドビューコントローラーとプロトコル

2番目のビューコントローラーのコードは

import UIKit

// protocol used for sending data back
protocol DataEnteredDelegate: AnyObject {
    func userDidEnterInformation(info: String)
}

class SecondViewController: UIViewController {

    // making this a weak variable so that it won't create a strong reference cycle
    weak var delegate: DataEnteredDelegate? = nil

    @IBOutlet weak var textField: UITextField!

    @IBAction func sendTextBackButton(sender: AnyObject) {

        // call this method on whichever class implements our delegate protocol
        delegate?.userDidEnterInformation(info: textField.text!)

        // go back to the previous view controller
        _ = self.navigationController?.popViewController(animated: true)
    }
}

protocolはView Controllerクラスの外にあることに注意してください。

それでおしまい。アプリを実行すると、2番目のView Controllerから最初のView Controllerにデータを送信できるようになります。


Swiftの最新の更新をいくつか考慮すると、これはまだ実装する一般的なパターンですか?
piofusco

4
私が見たほとんどすべてのSwiftの更新は、比較的マイナーな構文上の変更であり、ビューコントローラー間でのデータの受け渡し方法の変更ではありません。そのような大きな変更を知ったら、答えを更新します。
Suragch

2
offtopic-iOSには信じられないほど新しいビューコントローラーにパラメーターを渡すための醜い方法があります-呼び出しを行うときではなく、他の場所でパラメーターを設定する必要があります。Androidはこの点でより優れたアプローチを持っています。アクティビティを開始すると、その開始インテントを介してデータを渡すことができます(ほとんどの場合)。簡単です。キャストする必要はありません。戻り値を呼び出し元に返すことも必須であり、委任する必要はありません。もちろん、醜いアプローチを使用することも可能です。問題はありません))
Mixaz

1
@Himanshu、最初に2番目のビューコントローラーへの参照を取得します。次に、含まれているパブリック変数を更新します。
Suragch

8
@はちみつ。「デリゲート」という言葉は紛らわしいと思います。「労働者」という言葉を使いましょう。「ワーカー」(最初のビューコントローラー)は、「ボス」(2番目のビューコントローラー)が指示することをすべて実行します。「上司」は、その「労働者」が誰かを知りません。だれでもかまいません。したがって、最初のビューコントローラー(「ワーカー」クラス)では、私があなたの「ワーカー」になります。ラベルに何を書いていいか教えてください。したがって、secondViewController.delegate = self「私は上司の労働者であることに同意します」を意味します。別の例と詳細な説明については、この回答を参照してください。
Suragch 2016

136

MVCのMは「モデル」用であり、MVCパラダイムでは、モデルクラスの役割はプログラムのデータを管理することです。モデルはビューの反対です。ビューはデータの表示方法を知っていますが、データの処理方法については何も知りませんが、モデルはデータの操作方法についてすべて知っていますが、表示方法については何も知りません。モデルは複雑になる可能性がありますが、必ずしもそうである必要はありません。アプリのモデルは、文字列や辞書の配列と同じくらい単純な場合があります。

コントローラの役割は、ビューとモデルを仲介することです。したがって、1つ以上のビューオブジェクトと1つ以上のモデルオブジェクトへの参照が必要です。モデルが辞書の配列であり、各辞書がテーブルの1つの行を表しているとしましょう。アプリのルートビューにそのテーブルが表示されます。ファイルから配列をロードすることもできます。ユーザーがテーブルに新しい行を追加することを決定すると、ボタンをタップし、コントローラーが新しい(可変)辞書を作成して配列に追加します。行を埋めるために、コントローラーは詳細ビューコントローラーを作成し、それに新しい辞書を与えます。詳細ビューコントローラーはディクショナリを入力して戻ります。辞書はすでにモデルの一部であるため、他に何もする必要はありません。


95

iOSの別のクラスにデータを受信するには、さまざまな方法があります。例えば ​​-

  1. 別のクラスの割り当て後の直接初期化。
  2. 委任-データを返すため
  3. 通知-一度に複数のクラスにデータをブロードキャストするため
  4. に保存しています NSUserDefaults -後でアクセスするため
  5. シングルトンクラス
  6. データベースおよびplistなどのその他のストレージメカニズム

ただし、現在のクラスで割り当てが行われる別のクラスに値を渡すという単純なシナリオでは、最も一般的で好ましい方法は、割り当て後の値の直接設定です。これは次のように行われます:-

Controller1とController2の 2つのコントローラーを使用してそれを理解できます。

Controller1クラスでController2オブジェクトを作成して、渡される文字列値でプッシュしたいとします。これは次のように行うことができます:-

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj passValue:@"String"];
    [self pushViewController:obj animated:YES];
}

Controller2クラスの実装では、この関数は次のようになります。

@interface Controller2  : NSObject

@property (nonatomic , strong) NSString* stringPassed;

@end

@implementation Controller2

@synthesize stringPassed = _stringPassed;

- (void) passValue:(NSString *)value {

    _stringPassed = value; //or self.stringPassed = value
}

@end

次のように、Controller2クラスのプロパティを直接設定することもできます。

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj setStringPassed:@"String"];  
    [self pushViewController:obj animated:YES];
}

複数の値を渡すには、次のような複数のパラメータを使用できます。

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passValue:@“String1 andValues:objArray withDate:date]; 

または、共通の機能に関連する4つ以上のパラメーターを渡す必要がある場合は、値をModelクラスに保存し、そのmodelObjectを次のクラスに渡すことができます

ModelClass *modelObject = [[ModelClass alloc] init]; 
modelObject.property1 = _property1;
modelObject.property2 = _property2;
modelObject.property3 = _property3;

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passmodel: modelObject];

だからあなたが望むなら要するに-

1) set the private variables of the second class initialise the values by calling a custom function and passing the values.
2) setProperties do it by directlyInitialising it using the setter method.
3) pass more that 3-4 values related to each other in some manner , then create a model class and set values to its object and pass the object using any of the above process.

お役に立てれば


84

さらに調査した結果、プロトコルとデリゲートは正しい/ Appleがこれを行う方法を好んだようです。

私はこの例を使用してしまいました

View Controllerと他のオブジェクトの間でデータを共有する @ iPhone Dev SDK

正常に動作し、文字列と配列をビュー間で前後に渡すことができました。

ご助力いただきありがとうございます


3
プロトコルとデリゲートを使用せず、アンワインドを使用してください。
マルハル2013

1
@malhalストーリーボードを使用しない場合はどうなりますか?
Evan R

無駄なプロトコルやデリゲートも嫌いです。@malhal
DawnSong

@EvanRコードでセグエを作成および実行できます。それはすべて同じです。
DawnSong

1
基本的に、このページのQA全体は「コンテナビューの前の昔から」です。100万年もの間、プロトコルやデリゲートに煩わされることはありません。とにかく、画面で行うすべての小さなことはコンテナビューです。そのため、質問はもう存在しません。すべてのコンテナビューからのすべての参照が「上下」しています。
Fattie、

66

私はブロックを渡す最もシンプルでエレガントなバージョンを見つけました。返されるデータを待機するビューコントローラーを "A"として、ビューコントローラーを "B"として返しましょう。この例では、2つの値を取得します。1つ目はType1、2つ目はType2です。

Storyboardを使用する場合、最初のコントローラーは、例えばセグエの準備中にコールバックブロックを設定します。

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController isKindOfClass:[BViewController class]])
    {
        BViewController *viewController = segue.destinationViewController;

        viewController.callback = ^(Type1 *value1, Type2 *value2) {
            // optionally, close B
            //[self.navigationController popViewControllerAnimated:YES];

            // let's do some action after with returned values
            action1(value1);
            action2(value2);
        };

    }
}

"B"ビューコントローラは、コールバックプロパティBViewController.hを宣言する必要があります。

// it is important to use "copy"
@property (copy) void(^callback)(Type1 *value1, Type2 *value2);

コールバックを返すために必要な値を取得した後、実装ファイルBViewController.mで呼び出す必要があります。

if (self.callback)
    self.callback(value1, value2);

覚えておくべきことの1つは、ブロックを使用すると、ここで説明するような強い参照と__weak参照を管理する必要があることが多いということです。


なぜ値を個別のプロパティではなく、コールバックブロックのパラメーターにしないのですか?
Timuçin

56

与えられた回答の多くにはいくつかの良い情報がありますが、質問に完全に対処するものはありません。

この質問では、View Controller間で情報を渡すことについて尋ねます。与えられた特定の例は、ビュー間で情報を渡すことについて尋ねていますが、iOSへの自己記述の新しさを考えると、元の投稿者はおそらくビュー間ではなく、ビューコントローラー間で(ビューコントローラーからの関与なしで)意味していました。すべての答えは2つのビューコントローラーに焦点を当てているようですが、アプリが進化して、情報交換に3つ以上のビューコントローラーを含める必要がある場合はどうなりますか?

元の投稿者はシングルトンAppDelegateの使用についても尋ねました。これらの質問には答える必要があります。

完全な回答を求めているこの質問を見る他の人を助けるために、私はそれを提供しようと試みます。

アプリケーションシナリオ

非常に仮想的で抽象的な議論をするのではなく、具体的なアプリケーションを念頭に置いておくと役立ちます。2ビューコントローラーの状況と2ビュー以上のコントローラーの状況を定義するために、2つの具体的なアプリケーションシナリオを定義します。

シナリオ1:最大2つのView Controllerが情報を共有する必要があります。図1を参照してください。

元の問題の図

アプリケーションには2つのView Controllerがあります。ViewControllerA(データ入力フォーム)とView Controller B(製品リスト)があります。商品リストで選択したアイテムは、データ入力フォームのテキストボックスに表示されているアイテムと一致する必要があります。このシナリオでは、ViewControllerAとViewControllerBは互いに直接通信し、他のView Controllerとは通信しない必要があります。

シナリオ2:3 以上のView Controllerが同じ情報を共有する必要があります。図2を参照してください。

住宅在庫アプリケーション図

アプリケーションには4つのView Controllerがあります。家の在庫を管理するためのタブベースのアプリケーションです。3つのビューコントローラーは、同じデータの異なるフィルタービューを表示します。

  • ViewControllerA-高級品
  • ViewControllerB-非保険商品
  • ViewControllerC-家全体の在庫
  • ViewControllerD-新しいアイテムフォームの追加

個々のアイテムが作成または編集されるときは常に、他のビューコントローラーとも同期する必要があります。たとえば、ViewControllerDにボートを追加したが、まだ保証されていない場合、ユーザーがViewControllerA(豪華アイテム)に移動するとボートが表示され、ViewControllerC(家全体の在庫)にもボートが表示される必要があります。 ViewControllerB(非保険商品)。新しいアイテムの追加だけでなく、アイテムの削除(4つのView Controllerのいずれかから許可される場合もあります)、または既存のアイテムの編集(「新しいアイテムの追加フォーム」から許可される場合があります)にも注意する必要があります。編集用)。

すべてのView Controllerは同じデータを共有する必要があるため、4つのView Controllerすべてが同期を保つ必要があり、したがって、単一のView Controllerが基礎となるデータを変更するときはいつでも、他のすべてのView Controllerと何らかの通信が必要です。このシナリオでは、各ビューコントローラーが他のビューコントローラーと直接通信する必要がないことは明らかです。明らかでない場合は、20の異なるビューコントローラー(4つではなく)があるかどうかを検討してください。1つのビューコントローラーが変更を行うたびに、他の19のビューコントローラーのそれぞれに通知するのはどれほど難しく、エラーが発生しやすいでしょうか。

ソリューション:デリゲートとオブザーバーパターン、およびシングルトン

シナリオ1では、他の回答が示したように、いくつかの実行可能なソリューションがあります。

  • セゲス
  • 代議員
  • ビューコントローラーのプロパティを直接設定する
  • NSUserDefaults(実際には不適切な選択)

シナリオ2では、他の実行可能なソリューションがあります。

  • オブザーバーパターン
  • シングルトン

シングルトンインスタンスは、その寿命の間に存在する唯一のインスタンスであることは、クラスのインスタンスです。シングルトンは、それが単一のインスタンスであるという事実からその名前を取得します。通常、シングルトンを使用する開発者には、それらにアクセスするための特別なクラスメソッドがあります。

+ (HouseholdInventoryManager*) sharedManager; {
    static dispatch_once_t onceQueue;
    static HouseholdInventoryManager* _sharedInstance;

    // dispatch_once is guaranteed to only be executed once in the
    // lifetime of the application
    dispatch_once(&onceQueue, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

シングルトンが何であるかがわかったところで、シングルトンがオブザーバーパターンにどのように適合するかを説明しましょう。オブザーバーパターンは、あるオブジェクトが別のオブジェクトによる変更に応答するために使用されます。2番目のシナリオでは、4つの異なるビューコントローラーがあり、すべてが基になるデータの変更について知りたいと考えています。「基礎となるデータ」は、単一のインスタンスであるシングルトンに属している必要があります。「変更について知る」は、シングルトンに加えられた変更を監視することによって行われます。

家庭用在庫アプリケーションには、在庫アイテムのリストを管理するように設計されたクラスの単一インスタンスがあります。マネージャーは家庭用品のコレクションを管理します。以下は、データマネージャーのクラス定義です。

#import <Foundation/Foundation.h>

@class JGCHouseholdInventoryItem;

@interface HouseholdInventoryManager : NSObject
/*!
 The global singleton for accessing application data
 */
+ (HouseholdInventoryManager*) sharedManager;


- (NSArray *) entireHouseholdInventory;
- (NSArray *) luxuryItems;
- (NSArray *) nonInsuredItems;

- (void) addHouseholdItemToHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) editHouseholdItemInHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) deleteHoueholdItemFromHomeInventory:(JGCHouseholdInventoryItem*)item;
@end

ホームインベントリアイテムのコレクションが変更された場合、View Controllerはこの変更を認識する必要があります。上記のクラス定義は、これがどのように発生するかを明確にしていません。オブザーバーパターンに従う必要があります。ビューコントローラは、sharedManagerを正式に監視する必要があります。別のオブジェクトを観察するには2つの方法があります。

  • キー値監視(KVO)
  • NSNotificationCenter。

シナリオ2では、KVOを使用して監視できるHouseholdInventoryManagerの単一のプロパティがありません。簡単に観察できる単一のプロパティがないため、この場合、オブザーバーパターンはNSNotificationCenterを使用して実装する必要があります。4つのView Controllerはそれぞれ通知をサブスクライブし、SharedManagerは必要に応じて通知センターに通知を送信します。在庫マネージャは、View Controllerや、在庫アイテムのコレクションがいつ変更されたかを知りたい他のクラスのインスタンスについて何も知る必要はありません。NSNotificationCenterは、これらの実装の詳細を処理します。ビューコントローラーは通知をサブスクライブするだけで、データマネージャーは通知をポストするだけです。

多くの初心者プログラマは、常に1つのアプリケーションデリゲートが存在するという事実を利用しています。は、グローバルにアクセス可能なアプリケーションの存続期間中に利用しています。初心者のプログラマーは、この事実を使用して、アプリケーション内のどこからでもアクセスできるように、オブジェクトと機能をappDelegateに詰め込みます。AppDelegateがシングルトンだからといって、他のすべてのシングルトンを置き換える必要があるわけではありません。これは、1つのクラスに負担をかけすぎて、適切なオブジェクト指向のプラクティスを壊してしまうため、不適切なプラクティスです。各クラスには、簡単に説明できる明確な役割が必要です。多くの場合、クラスの名前だけで説明されます。

アプリケーションデリゲートが肥大化し始めたら、機能をシングルトンに削除し始めます。たとえば、コアデータスタックはAppDelegateに残さず、独自のクラスであるcoreDataManagerクラスに配置する必要があります。

参考文献


41

OPはビューコントローラーについて言及していませんが、答えの多くがそうであるように、あるビューコントローラーから別のビューコントローラーにデータを渡したい場合に、LLVMのいくつかの新機能でこれを簡単にできるようにしたいと思いました。結果を取り戻す。

ストーリーボードセグエ、ARCおよびLLVMブロックは、これをこれまでになく簡単にしてくれます。上記のいくつかの回答は、ストーリーボードとセグエに言及していますが、委任に依拠しています。デリゲートの定義は確かに機能しますが、ポインターやコードブロックを渡す方が簡単な場合もあります。

UINavigatorとセグエを使用すると、情報を従属のコントローラーに渡し、情報を戻す簡単な方法があります。ARCは、NSObjectsから派生したものへのポインターの受け渡しを簡単にするため、従属コントローラーがデータを追加/変更/変更する場合は、可変インスタンスへのポインターを渡します。ブロックを使用すると、アクションを簡単に渡すことができるため、従属コントローラが上位レベルのコントローラでアクションを呼び出すようにするには、ブロックを渡します。自分にとって意味のある任意の数の引数を受け入れるようにブロックを定義します。APIを設計して複数のブロックを使用することもできます。

セグエ接着剤の簡単な2つの例を次に示します。1つ目は入力用に渡された1つのパラメータを示し、2つ目は出力用に渡された単純なものです。

// Prepare the destination view controller by passing it the input we want it to work on
// and the results we will look at when the user has navigated back to this controller's view.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [[segue destinationViewController]

     // This parameter gives the next controller the data it works on.
     segueHandoffWithInput:self.dataForNextController

     // This parameter allows the next controller to pass back results
     // by virtue of both controllers having a pointer to the same object.
     andResults:self.resultsFromNextController];
}

この2番目の例は、2番目の引数にコールバックブロックを渡すことを示しています。私はブロックを使用するのが好きです。それは、関連する詳細をソース(より高いレベルのソース)で一緒に保つためです。

// Prepare the destination view controller by passing it the input we want it to work on
// and the callback when it has done its work.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [[segue destinationViewController]

     // This parameter gives the next controller the data it works on.
     segueHandoffWithInput:self.dataForNextController

     // This parameter allows the next controller to pass back results.
     resultsBlock:^(id results) {
         // This callback could be as involved as you like.
         // It can use Grand Central Dispatch to have work done on another thread for example.
        [self setResultsFromNextController:results];
    }];
}

41

ViewController 2(destination)からviewController 1(Source)にデータを渡すことは、より興味深いことです。あなたがstoryBoardを使用していると仮定すると、それらは私が見つけたすべての方法です:

  • 委任
  • 通知
  • ユーザーデフォルト
  • シングルトン

それらはすでにここで議論されました。

他にも方法があることがわかりました。

-ブロックコールバックの使用:

prepareForSegueVC1のメソッドで使用する

NextViewController *destinationVC = (NextViewController *) segue.destinationViewController;
[destinationVC setDidFinishUsingBlockCallback:^(NextViewController *destinationVC)
{
    self.blockLabel.text = destination.blockTextField.text;
}];

-ストーリーボードの使用(巻き戻し)(終了)

次のように、VC 1でUIStoryboardSegue引数を使用してメソッドを実装します。

-(IBAction)UnWindDone:(UIStoryboardSegue *)segue { }

storyBoardで、「戻る」ボタンをvcの緑の終了ボタン(巻き戻し)にフックします。これで、「戻る」というセグエがあるため、VC2のprepareForSegueのdestinationViewControllerプロパティを使用して、戻る前にVC1のプロパティを変更できます。

  • ストーリーボードを使用する別のオプションUndwind(終了)-VC1で記述した方法を使用できます

    -(IBAction)UnWindDone:(UIStoryboardSegue *)segue {
        NextViewController *nextViewController = segue.sourceViewController;
        self.unwindLabel.text = nextViewController.unwindPropertyPass;
    } 

    VC1のprepareForSegueでは、共有したいプロパティを変更できます。

両方の巻き戻しオプションで、ボタンのタグプロパティを設定し、それをprepareForSegueで確認できます。

ディスカッションに何か追加したいと思います。

:)乾杯。


40

データを共有する方法は複数あります。

  1. を使用すると、いつでもデータを共有できますNSUserDefaults。選択したキーに関して共有する値を設定NSUserDefaultし、次のビューコントローラーでそのキーに関連付けられている値を取得します。

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]
    [[NSUserDefaults standardUserDefaults] objectForKey:key]
  2. でプロパティを作成できますviewcontrollerAviewcontrollerAin のオブジェクトを作成し、viewcontrollerBそのプロパティに目的の値を割り当てます。

  3. このためのカスタムデリゲートを作成することもできます。


30
NSUserDefaultsの一般的な目的は、アプリの実行間で持続するユーザー設定を保存することです。そのため、ここに保存されているものはすべて、明示的に削除しない限りここに残ります。これを使用して、アプリ内のビューコントローラー(またはその他のオブジェクト)間で情報を受け渡すことは非常に悪い考えです。
ホセ・ゴンサレス2014年

30

あるコントローラから別のコントローラにデータを渡したい場合は、このコードを試してください

FirstViewController.h

@property (nonatomic, retain) NSString *str;

SecondViewController.h

@property (nonatomic, retain) NSString *str1;

FirstViewController.m

- (void)viewDidLoad
   {
     // message for the second SecondViewController
     self.str = @"text message";

     [super viewDidLoad];
   }

-(IBAction)ButtonClicked
 {
   SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
   secondViewController.str1 = str;
  [self.navigationController pushViewController:secondViewController animated:YES];
 }

29

これは非常に古い回答であり、これは反パターンです。デリゲートを使用してください。このアプローチは使用しないでください!!

1. 2番目のView Controllerに最初のView Controllerのインスタンスを作成し、そのプロパティを作成します@property (nonatomic,assign)

2.SecondviewControllerこのView Controllerのインスタンスを割り当てます。

2.選択操作が完了したら、配列を最初のView Controllerにコピーします。SecondViewをアンロードすると、FirstViewは配列データを保持します。

お役に立てれば。


2
ビューコントローラー間に非常に隆起したリンクが作成されるため、これが正しい方法だとは思いません。本当にMVCにこだわらない。
マット価格

1
MVCに厳密に従う場合は、NSNotificationCenterを使用し、ViewControllerAからViewControllerBにメソッドを呼び出すことができます。これを確認すると、u
kaar3k

28

私はこのソリューションを長い間探していましたが、Atlastはそれを見つけました。まず、SecondViewController.hファイルのすべてのオブジェクトを次のように宣言します。

@interface SecondViewController: UIviewController 
{
    NSMutableArray *myAray;
    CustomObject *object;
}

次に、実装ファイルで、このようなオブジェクトにメモリを割り当てます

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
     if (self) 
     {
         // Custom initialization
         myAray=[[NSMutableArray alloc] init];
         object=[[CustomObject alloc] init];
     }
     return self;
}

これでArray、オブジェクトとオブジェクトのメモリが割り当てられました。今、あなたはこれを押す前にその記憶を満たすことができますViewController

SecondViewController.hに移動し、2つのメソッドを記述します

-(void)setMyArray:(NSArray *)_myArray;
-(void)setMyObject:(CustomObject *)_myObject;

実装ファイルで関数を実装できます

-(void)setMyArray:(NSArray *)_myArray
{
     [myArra addObjectsFromArray:_myArray];
}
-(void)setMyObject:(CustomObject *)_myObject
{
     [object setCustomObject:_myObject];
}

あなたCustomObjectはそれにセッター関数を持っている必要があることを期待しています。

これで基本的な作業は完了です。プッシュしたい場所に行きSecondViewController、次のことを行います

SecondViewController *secondView= [[SecondViewController alloc] initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]] ;
[secondView setMyArray:ArrayToPass];
[secondView setMyObject:objectToPass];
[self.navigationController pushViewController:secondView animated:YES ];

スペルミスに注意してください。


24

これはそれを行う方法ではありません。デリゲートを使用する必要があります。2つのビューコントローラーViewController1とViewController2があり、このチェックは最初のものにあり、その状態が変化すると、ViewController2で何かを実行したいとします。適切な方法でそれを達成するには、以下を実行する必要があります。

新しいファイルをプロジェクトに追加します(Objective-C Protocol)File-> New、今ではそれにViewController1Delegateなどの名前を付け、@ interfaceディレクティブと@endディレクティブの間に記述します。

@optional

- (void)checkStateDidChange:(BOOL)checked;

次に、ViewController2.hに移動して、

#import "ViewController1Delegate.h"

次に、その定義を

@interface ViewController2: UIViewController<ViewController1Delegate>

次にViewController2.mに移動し、実装内に次の行を追加します。

- (void)checkStateDidChange:(BOOL)checked {
     if (checked) {
           // Do whatever you want here
           NSLog(@"Checked");
     }
     else {
           // Also do whatever you want here
           NSLog(@"Not checked");
     }
}

次に、ViewController1.hに移動し、次のプロパティを追加します。

@property (weak, nonatomic) id<ViewController1Delegate> delegate; 

ここで、何らかのイベントの後でViewController2内にViewController1を作成する場合は、NIBファイルを使用して次のようにする必要があります。

ViewController1* controller = [[NSBundle mainBundle] loadNibNamed:@"ViewController1" owner:self options:nil][0];
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];

これで準備はすべて整いました。ViewController1で変更されたチェックのイベントを検出するたびに、以下を実行するだけです。

[delegate checkStateDidChange:checked]; // You pass here YES or NO based on the check state of your control

あなたの質問を正しく理解できなかったのかどうかわからないことがあれば教えてください。


23

データをあるビューコントローラから別のビューコントローラに送信する場合は、次の方法があります。

たとえば、viewControllers:viewControllerAとviewControllerBがあるとします。

今viewControllerB.hで

@interface viewControllerB : UIViewController {

  NSString *string;
  NSArray *array;

}

- (id)initWithArray:(NSArray)a andString:(NSString)s;

viewControllerB.m内

#import "viewControllerB.h"

@implementation viewControllerB

- (id)initWithArray:(NSArray)a andString:(NSString)s {

   array = [[NSArray alloc] init];
   array = a;

   string = [[NSString alloc] init];
   string = s;

}

viewControllerA.m

#import "viewControllerA.h"
#import "viewControllerB.h"

@implementation viewControllerA

- (void)someMethod {

  someArray = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];
  someString = [NSString stringWithFormat:@"Hahahahaha"];

  viewControllerB *vc = [[viewControllerB alloc] initWithArray:someArray andString:someString];

  [self.navigationController pushViewController:vc animated:YES];
  [vc release];

}

したがって、デリゲートを設定せずに、viewControllerAからviewControllerBにデータを渡すことができます。;)


1
プロジェクトでurコードを使用しようとしましたが、viewcontrollerBの値を取得できません。何が問題なのか教えてもらえますか?
X-Coder 2013年

1
@Ajitthala新しい質問にコードを貼り付けられますか?私はあなたの問題を解決しようとします。:)
Aniruddh Joshi 2013

1
initメソッドを使用せず、viewcontroller AからvcB.string = @ "asdf"のようなことをするのは間違っていますか?
khanh.tran.vinh 2013

1
@ khanh.tran.vinh ARCを使用しているかどうかによって異なります。
Aniruddh Joshi 2013

21

私はこれが暴力的な主題であることを知っていますが、SWIFTの傾斜でこの質問に答えたい、そして必要最低限​​の例が欲しい人のために、セグエを使用して回避する場合にデータを渡すための私の頼りになる方法をここに示します。

上記と同様ですが、ボタンやラベルなどはありません。1つのビューから次のビューにデータを渡すだけです。

ストーリーボードのセットアップ

3つの部分があります。

  1. 送り主
  2. セグエ
  3. 受信機

これは、セグエを挟んだ非常にシンプルなビューレイアウトです。


非常にシンプルなビューのレイアウト。 注:ナビゲーションコントローラはありません


これが送信者の設定です


送り主


これが受信機の設定です。


受信機


最後に、セグエのセットアップ。


セグエ識別子


ビューコントローラー

これはシンプルに保つため、ボタンではなくアクションではなく、アプリケーションが読み込まれたときに送信側から受信側にデータを移動し、送信された値をコンソールに出力しています。

このページは、最初にロードされた値を受け取り、それを渡します。

import UIKit


class ViewControllerSender: UIViewController {

    // THE STUFF - put some info into a variable
    let favoriteMovie = "Ghost Busters"

    override func viewDidAppear(animated: Bool) {
        // PASS IDENTIFIER - go to the recieving view controller.
        self.performSegueWithIdentifier("goToReciever", sender: self)
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        //GET REFERENCE - ...to the receiver view.
        var viewControllerReceiver = segue.destinationViewController as? ViewControllerReceiver

        //PASS STUFF - pass the variable along to the target.
        viewControllerReceiver!.yourFavMovie = self.favoriteMovie

    }

}

このページは、ロード時に変数の値をコンソールに送信するだけです。この時点で、私たちのお気に入りの映画はその変数に含まれるはずです。

import UIKit

class ViewControllerReceiver: UIViewController {

    //Basic empty variable waiting for you to pass in your fantastic favorite movie.
    var yourFavMovie = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        //And now we can view it in the console.
        println("The Movie is \(self.yourFavMovie)")

    }   
}

セグエを使用したいが、ナビゲーションコントローラーの下にページがない場合は、この方法で対処できます。

実行すると、自動的にレシーバービューに切り替わり、送信者から受信者に値が渡され、コンソールに値が表示されます。

ゴーストバスターズは古典的な人々です。


19

私の場合、アプリのほぼすべての場所からデータへのアクセスを許可するグローバルオブジェクトとして機能できるシングルトンクラスを使用しました。最初に、シングルトンクラスを作成します。「Objective-Cシングルトンはどのように見えるべきか」のページを参照してくださいオブジェクトをグローバルにアクセスできるようにするために行っappName_Prefix.pchたのは、すべてのクラスにインポート文を適用するために単にインポートすることでした。このオブジェクトにアクセスして使用するには、クラスメソッドを実装して、独自の変数を含む共有インスタンスを返します


これが正解です。シングルトンを「モデル」として使用してください。カレブが言うように、「アプリのモデルは文字列の配列と同じくらい簡単かもしれません」Swiftでシングルトン実行するのは非常に簡単です。(非常に単純なので、ここで言及する価値はありません。Googleだけです。)新しいプログラマにとって、シングルトン作成することは、以前はお尻の痛みであったことを理解する価値があります。ただし、シングルトンはiOSプログラミングの中心です-Appleが行うことはすべてシングルトンです。だからこそ、Appleはようやくシングルトンを適切に作成するために(Swiftで)trtvialにしました。
Fattie、2016年

1
ただし、最近(2016年以降)は「すべてがiOSのコンテナビュー」であることに注意してください。画面上で行うすべてのことは、小さなコンテナビューを作成します。コンテナービューの参照を「上」および「下」に参照することはかなり簡単です(ただし、Appleは将来的にこれを簡単にする予定です)。とにかく、ほとんどすべてのコンテナービューに対してこれを行います。だから、とにかくそれをやった場合-あなたは答えを持っています。シングルトンは必要ありません。コンテナビューの概要... stackoverflow.com/a/23403979/294884
Fattie

19

スウィフト5

まあマットプライスの回答はデータを渡すのに完全に問題ありませんが、元の投稿がObjective-Cにあるため、新しい構文とメソッド/フレームワークのために新しいプログラマーが難しいと思うので、最新のSwiftバージョンで書き直します。

View Controller間でデータを受け渡すには、複数のオプションがあります。

  1. ナビゲーションコントローラープッシュの使用
  2. セグエを使う
  3. デリゲートを使用する
  4. 通知オブザーバーの使用
  5. ブロックの使用

私は彼のロジックを最新のiOSフレームワークでSwiftで書き換えます


ナビゲーションコントローラプッシュを介してデータを渡すViewControllerAからViewControllerBへ

ステップ1. ViewControllerBで変数を宣言する

var isSomethingEnabled = false

ステップ2. ViewControllerBのViewDidLoadメソッドで変数を出力する

override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through segue, navigation push
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }

手順3. Navigation Controllerを介してプッシュしながらViewControllerAにデータを渡す

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
        viewControllerB.isSomethingEnabled = true
        if let navigator = navigationController {
            navigator.pushViewController(viewControllerB, animated: true)
        }
    }

だからここに完全なコードがあります:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:Passing Data through Navigation PushViewController
    @IBAction func goToViewControllerB(_ sender: Any) {

        if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.isSomethingEnabled = true
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    //MARK:  - Variable for Passing Data through Navigation push   
    var isSomethingEnabled = false

    override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through navigation push
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }
}

Segueを介したデータの受け渡しViewControllerAからViewControllerBへ

ステップ1. ViewControllerAからViewControllerBにSegueを作成し、以下に示すように、ストーリーボードでIdentifier = showDetailSegueを指定します。

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

手順2. ViewControllerBでisSomethingEnabledという名前の実行可能変数を宣言し、その値を出力します。

ステップ3. ViewControllerAで、Segueを渡すときにisSomethingEnabledの値を渡します。

だからここに完全なコードがあります:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:  - - Passing Data through Segue  - - 
    @IBAction func goToViewControllerBUsingSegue(_ sender: Any) {
        performSegue(withIdentifier: "showDetailSegue", sender: nil)
    }

    //Segue Delegate Method
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "showDetailSegue") {
            let controller = segue.destination as? ViewControllerB
            controller?.isSomethingEnabled = true//passing data
        }
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {
    var isSomethingEnabled = false

    override func viewDidLoad() {
        super.viewDidLoad()
        //Print value received through segue
        print("Value of 'isSomethingEnabled' from ViewControllerA : ", isSomethingEnabled)
    }
}

デリゲートを介してデータを渡すViewControllerBからViewControllerAへ

ステップ1. ViewControllerBファイルでプロトコルViewControllerBDelegateを宣言するが、クラス外

protocol ViewControllerBDelegate: NSObjectProtocol {

    // Classes that adopt this protocol MUST define
    // this method -- and hopefully do something in
    // that definition.
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?)
}

ステップ2. ViewControllerBでデリゲート変数インスタンスを宣言する

var delegate: ViewControllerBDelegate?

ステップ3. ViewControllerBのviewDidLoadメソッド内のデリゲートのデータを送信する

delegate?.addItemViewController(self, didFinishEnteringItem: "Data for ViewControllerA")

ステップ4. ViewControllerAでViewControllerBDelegateを確認する

class ViewControllerA: UIViewController, ViewControllerBDelegate  {
// to do
}

手順5. ViewControllerAにデリゲートを実装することを確認する

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.delegate = self//confirming delegate
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }

手順6. ViewControllerAでデータを受信するためのデリゲートメソッドを実装する

func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?) {
        print("Value from ViewControllerB's Delegate", item!)
    }

だからここに完全なコードがあります:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController, ViewControllerBDelegate  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //Delegate method
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?) {
        print("Value from ViewControllerB's Delegate", item!)
    }

    @IBAction func goToViewControllerForDelegate(_ sender: Any) {

        if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.delegate = self
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }
    }
}

ViewControllerB

import UIKit

//Protocol decleare
protocol ViewControllerBDelegate: NSObjectProtocol {
    // Classes that adopt this protocol MUST define
    // this method -- and hopefully do something in
    // that definition.
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?)
}

class ViewControllerB: UIViewController {
    var delegate: ViewControllerBDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        //MARK:  - - - -  Set Data for Passing Data through Delegate  - - - - - -
        delegate?.addItemViewController(self, didFinishEnteringItem: "Data for ViewControllerA")
    }
}

通知オブザーバーを介したデータの受け渡しViewControllerBからViewControllerAへ

ステップ1. ViewControllerBの通知オブザーバーにデータを設定して投稿する

let objToBeSent = "Test Message from Notification"
        NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)

ステップ2. ViewControllerAに通知オブザーバーを追加する

NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)

ステップ3. ViewControllerAで通知データ値を受信する

@objc func methodOfReceivedNotification(notification: Notification) {
        print("Value of notification : ", notification.object ?? "")
    }

だからここに完全なコードがあります:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController{

    override func viewDidLoad() {
        super.viewDidLoad()

        // add observer in controller(s) where you want to receive data
        NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)
    }

    //MARK: Method for receiving Data through Post Notification 
    @objc func methodOfReceivedNotification(notification: Notification) {
        print("Value of notification : ", notification.object ?? "")
    }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //MARK:Set data for Passing Data through Post Notification
        let objToBeSent = "Test Message from Notification"
        NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)
    }
}

ブロックを介したデータの受け渡しViewControllerBからViewControllerAへ

ステップ1. ViewControllerBでブロックを宣言する

var authorizationCompletionBlock:((Bool)->())?= {_ in}

ステップ2. ViewControllerBのブロックにデータを設定する

if authorizationCompletionBlock != nil
        {
            authorizationCompletionBlock!(true)
        }

ステップ3. ViewControllerAでブロックデータを受信する

//Receiver Block
                controller!.authorizationCompletionBlock = { isGranted in
                    print("Data received from Block is :", isGranted)
                }

だからここに完全なコードがあります:

ViewControllerA

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    //MARK:Method for receiving Data through Block
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if (segue.identifier == "showDetailSegue") {
                let controller = segue.destination as? ViewControllerB
                controller?.isSomethingEnabled = true

                //Receiver Block
                controller!.authorizationCompletionBlock = { isGranted in
                    print("Data received from Block is :", isGranted)
                }
            }
        }
}

ViewControllerB

import UIKit

class ViewControllerB: UIViewController {

    //MARK:Variable for Passing Data through Block
    var authorizationCompletionBlock:((Bool)->())? = {_ in}

    override func viewDidLoad() {
        super.viewDidLoad()

        //MARK:Set data for Passing Data through Block
        if authorizationCompletionBlock != nil
        {
            authorizationCompletionBlock!(true)
        }
    }
}

あなたは私のGitHubで完全なサンプルアプリケーションを見つけることができますこれについて質問がある場合は私に知らせてください。


18

以下のようにFirstViewControllerからSecondViewControllerにデータを渡す

例えば:

FirstViewController String value as

StrFirstValue = @"first";

以下のステップを使用して、この値を2番目のクラスに渡すことができます

1> SecondViewController.hファイルに文字列オブジェクトを作成する必要があります

NSString *strValue;

2> .hファイルの宣言の下で、以下のようにプロパティを宣言する必要があります

@property (strong, nonatomic)  NSString *strSecondValue;

3>ヘッダー宣言の下のFirstViewController.mファイルでその値を合成する必要があります

@synthesize strValue;

そしてFirstViewController.hで:

@property (strong, nonatomic)  NSString *strValue;

4> FirstViewControllerで、どのメソッドから2番目のビューに移動するかを、そのメソッドのコードの下に記述してください。

SecondViewController *secondView= [[SecondViewController alloc]     
initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]];

[secondView setStrSecondValue:StrFirstValue];

[self.navigationController pushViewController:secondView animated:YES ];

SecondViewControllerに入った後、FirstViewControllerにデータをどのように渡しますか?
Bruno、2015年

18

私は現在、MCViewFactoryと呼ばれるプロジェクトを通じて、この問題のオープンソースソリューションに貢献しています。

https://github.com/YetiHQ/manticore-iosviewfactory

このアイデアは、Androidのインテントパラダイムを模倣したもので、グローバルファクトリを使用してどのビューを表示しているかを管理し、「インテント」を使用してビュー間でデータを切り替えて渡します。すべてのドキュメントはgithubページにありますが、ここではいくつかのハイライトがあります:

ファクトリを初期化するときに、すべてのビューを.XIBファイルに設定し、アプリデリゲートに登録します。

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

// the following two lines are optional. 
[factory registerView:@"YourSectionViewController"]; 

これで、VCで、新しいVCに移動してデータを渡したいときはいつでも、新しいインテントを作成し、そのディクショナリ(savedInstanceState)にデータを追加します。次に、工場の現在の意図を設定します。

MCIntent* intent = [MCIntent intentWithSectionName:@"YourSectionViewController"];
[intent setAnimationStyle:UIViewAnimationOptionTransitionFlipFromLeft];
[[intent savedInstanceState] setObject:@"someValue" forKey:@"yourKey"];
[[intent savedInstanceState] setObject:@"anotherValue" forKey:@"anotherKey"];
// ...
[[MCViewModel sharedModel] setCurrentSection:intent];

これに準拠するすべてのビューはMCViewControllerのサブクラスである必要があります。これにより、新しいonResume:メソッドをオーバーライドして、渡したデータにアクセスできるようになります。

-(void)onResume:(MCIntent *)intent {
    NSObject* someValue = [intent.savedInstanceState objectForKey:@"yourKey"];
    NSObject* anotherValue = [intent.savedInstanceState objectForKey:@"anotherKey"];

    // ...

    // ensure the following line is called, especially for MCSectionViewController
    [super onResume:intent];
}

このソリューションが役立つ/興味深いと思う人がいるかもしれません。


次に、すべてのコントローラオブジェクトは、スコープ内のすべての登録済み辞書を取得/設定できますか?これに反対票を投じます。
イタチ

15

次にプロパティを作成し、view controller .hゲッターとセッターを定義します。

これpropertyをnextVCのNextVC.hに追加します

@property (strong, nonatomic) NSString *indexNumber;

追加

@synthesize indexNumber; NextVC.m

そして最後

NextVC *vc=[[NextVC alloc]init];

vc.indexNumber=@"123";

[self.navigationController vc animated:YES];

11

これを行う方法はたくさんありますが、正しい方法を選択することが重要です。おそらく、最も大きなアーキテクチャ上の決定の1つは、モデルコードをアプリ全体で共有またはアクセスする方法にあります。

私はしばらく前にこれについてブログ記事を書きました:モデルコードの共有。これが簡単な要約です:

共有データ

1つのアプローチは、ビューコントローラー間でモデルオブジェクトへのポインターを共有することです。

  • データを設定するためのビューコントローラー(ナビゲーションまたはタブバーコントローラー内)のブルートフォース反復
  • prepareForSegue(ストーリーボードの場合)またはinit(プログラマティックの場合)にデータを設定します

セグエの準備が最も一般的であるため、ここに例があります:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var next = segue.destinationViewController as NextViewController
    next.dataSource = dataSource
}

独立したアクセス

別のアプローチは、一度にデータでいっぱいの画面を処理し、ビューコントローラーを相互に結合する代わりに、各ビューコントローラーを独立して取得できる単一のデータソースに結合します。

これを確認した最も一般的な方法は、シングルトンインスタンスです。したがって、シングルトンオブジェクトがあったDataAccess場合、UIViewControllerのviewDidLoadメソッドで次のことを実行できます。

func viewDidLoad() {
    super.viewDidLoad()
    var data = dataAccess.requestData()
}

データの受け渡しにも役立つ追加ツールがあります。

  • Key-Value監視
  • NSNotification
  • コアデータ
  • NSFetchedResultsController
  • 情報源

コアデータ

Core Dataの良いところは、逆の関係にあるということです。したがって、NotesViewControllerにnotesオブジェクトを与えるだけの場合は、ノートブックなどの他のオブジェクトと逆の関係になるためです。NotesViewControllerのノートブックのデータが必要な場合は、次のようにしてオブジェクトグラフをさかのぼることができます。

let notebookName = note.notebook.name

これについて詳しくは、ブログの投稿:モデルコードの共有をご覧ください。


10

NewsViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  [tbl_View deselectRowAtIndexPath:indexPath animated:YES];
  News *newsObj = [newstitleArr objectAtIndex:indexPath.row];
  NewsDetailViewController *newsDetailView = [[NewsDetailViewController alloc] initWithNibName:@"NewsDetailViewController" bundle:nil];

  newsDetailView.newsHeadlineStr = newsObj.newsHeadline;

  [self.navigationController pushViewController:newsDetailView animated:YES];
}

NewsDetailViewController.h

@interface NewsDetailViewController : UIViewController
@property(nonatomic,retain) NSString *newsHeadlineStr;
@end

NewsDetailViewController.m

@synthesize newsHeadlineStr;

10

委任は、.xibファイルを使用している場合にそのような操作を実行する唯一のソリューションですが、前述のすべての回答は、storyboard委任を使用する必要がある.xibsファイルに対するものです。それが唯一の解決策です。

別の解決策は、シングルトンクラスパターンを使用して一度初期化し、アプリ全体で使用することです。


10

ViewControlerOneからViewControllerTwoにデータを渡したい場合は、これらを試してください。

ViewControlerOne.hでこれらを実行します

 @property (nonatomic, strong) NSString *str1;

これらをViewControllerTwo.hで行います

 @property (nonatomic, strong) NSString *str2;

ViewControllerTwo.mでstr2を合成する

@interface ViewControllerTwo ()
@end
@implementation ViewControllerTwo
@synthesize str2;

ViewControlerOne.mでこれらを行う

 - (void)viewDidLoad
 {
   [super viewDidLoad];

  // Data or string you wants to pass in ViewControllerTwo..
  self.str1 = @"hello world";

 }

ボタンのクリックイベントでこれを行います。

-(IBAction)ButtonClicked
{ //Navigation on buttons click event from ViewControlerOne to ViewControlerTwo with transferring data or string..
  ViewControllerTwo *objViewTwo=[self.storyboard instantiateViewControllerWithIdentifier:@"ViewControllerTwo"];
  obj.str2=str1;
  [self.navigationController pushViewController: objViewTwo animated:YES];
}

ViewControllerTwo.mでこれらを行います

- (void)viewDidLoad
{
 [super viewDidLoad];
  NSLog(@"%@",str2);
}

10

アプリのデリゲートにデータを保存して、アプリケーションのView Controller全体にアクセスできます。アプリのデリゲートの共有インスタンスを作成するだけです

AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

例えば

を宣言するNSArray object *arrayXYZと、任意のビューコントローラでアクセスできますappDelegate.arrayXYZ


これはハッカソンの選択方法です
Hai Feng Kao

9

データをあるビューコントローラから別のビューコントローラに送信する場合は、次の方法があります。

たとえば、viewControllers:ViewControllerおよびNewViewControllerがあるとします。

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
{
    IBOutlet UITextField *mytext1,*mytext2,*mytext3,*mytext4;
}

@property (nonatomic,retain) IBOutlet UITextField *mytext1,*mytext2,*mytext3,*mytext4;

-(IBAction)goToNextScreen:(id)sender;

@end

ViewController.m

#import "ViewController.h"

#import "NewViewController.h"

@implementation ViewController
@synthesize mytext1,mytext2,mytext3,mytext4;

-(IBAction)goToNextScreen:(id)sender
{
    NSArray *arr = [NSArray arrayWithObjects:mytext1.text,mytext2.text,mytext3.text,mytext4.text, nil];


    NewViewController *newVc = [[NewViewController alloc] initWithNibName:@"NewViewController" bundle:nil];

    newVc.arrayList = arr;

    [self.navigationController pushViewController:newVc animated:YES];

}

NewViewController.h内

#import <UIKit/UIKit.h>

@interface NewViewController : UITableViewController
{
    NSArray *arrayList;

    NSString *name,*age,*dob,*mobile;

}

@property(nonatomic, retain)NSArray *arrayList;

@end

NewViewController.m

#import "NewViewController.h"

#import "ViewController.h"

@implementation NewViewController
@synthesize arrayList;

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{

    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{

    // Return the number of rows in the section.
    return [arrayList count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];      
    }
    // Configure the cell...
    cell.textLabel.text = [arrayList objectAtIndex:indexPath.row];
    return cell;


}

@end

このようにして、あるViewControllerから別のView Controllerにデータを渡すことができます...


8

ユーザーが選択したものをキャンセルできる場合にデータをコミットまたは破棄するNSProxyに基づくモデルオブジェクトとモックオブジェクトのアイデアが気に入っています。

単一のオブジェクトまたはいくつかのオブジェクトであるため、データを簡単に渡すことができます。たとえば、UINavigationControllerコントローラーの場合、モデルへの参照を保持でき、プッシュされたすべてのビューコントローラーがナビゲーションコントローラーから直接アクセスできます。


8

私は、このdidSelectRowAtPath方法を使用してこれを複雑にしている多くの人々を見てきました。私の例ではCore Dataを使用しています。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    //this solution is for using Core Data
    YourCDEntityName * value = (YourCDEntityName *)[[self fetchedResultsController] objectAtIndexPath: indexPath];

    YourSecondViewController * details = [self.storyboard instantiateViewControllerWithIdentifier:@"nameOfYourSecondVC"];//make sure in storyboards you give your second VC an identifier

    //Make sure you declare your value in the second view controller
    details.selectedValue = value;

    //Now that you have said to pass value all you need to do is change views
    [self.navigationController pushViewController: details animated:YES];

}

メソッド内の4行のコードで完了です。


6

実際に機能するビューコントローラー通信を実行するさまざまな方法を提供するこの質問に対する多くの答えがありますが、どれが実際に使用するのが最適で、どれを避けるべきかについてはどこにも言及していません。

実際には、私の意見では、いくつかのソリューションのみが推奨されています。

  • データを転送するには:
    • ストーリーボードとセグエを使用するときのprepare(for:sender:)方法をオーバーライドするUIViewController
    • コードを介してビューコントローラーの遷移を実行するときに、初期化子またはプロパティを介してデータを渡す
  • データを逆方向に渡すには
    • アプリの共有状態を更新します(上記の方法のいずれかを使用して、ビューコントローラー間で転送できます)
    • 委任を使用する
    • くつろぐセグエを使う

使用しないことをお勧めするソリューション:

  • 委任を使用する代わりに以前のコントローラーを直接参照する
  • シングルトンを介したデータの共有
  • アプリのデリゲートを介してデータを渡す
  • ユーザーデフォルトによるデータの共有
  • 通知を介してデータを渡す

これらのソリューションは、短期的には機能しますが、あまりにも多くの依存関係を導入し、アプリのアーキテクチャを混乱させ、後でさらに多くの問題を引き起こします。

興味のある方のために、私はこれらの点をより深く取り上げ、さまざまな欠点を強調するいくつかの記事を書きました:

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.