ビューが与えられた場合、どうすればそのviewControllerを取得できますか?


141

へのポインタがありUIViewます。どうすればアクセスできますUIViewControllerか? [self superview]は別UIViewですが、ではありませんUIViewControllerか?


このスレッドには答えがあると思います:iPhoneのUIViewからUIViewControllerにアクセスしますか?
Ushox

基本的に、自分のビューが閉じられるときに、viewControllerのviewWillAppearを呼び出そうとしています。ビューは、タップを検出して[self removeFromSuperview]を呼び出しているビュー自体によって閉じられています。viewControllerは、viewWillAppear / WillDisappear / DidAppear / DidDisappear自体を呼び出していません。
mahboudz

私のビューが閉じられているので、viewWillDisappearを呼び出そうとしているのです。
mahboudz 2009

回答:


42

はい、これsuperviewはビューを含むビューです。MVCの原則に反するため、ビューはどちらがビューコントローラーであるかを正確に知る必要はありません。

一方、コントローラーは、それが担当するビュー(self.view = myView)を認識しており、通常、このビューは処理するメソッド/イベントをコントローラーに委任します。

通常、ビューへのポインターの代わりに、コントローラーへのポインターが必要です。コントローラーは、制御ロジックを実行するか、ビューに何かを渡すことができます。


24
MVCの原則に反するかどうかはわかりません。どの時点でも、ビューには1つのビューコントローラーしかありません。メッセージを返すために到達できるのは自動機能であり、達成するために(追跡するプロパティを追加することによって)達成しなければならない機能ではありません。ビューについても同じことが言えます。自分が誰であるかを知る必要があるのはなぜですか。または、他の兄弟ビューがあるかどうか。しかし、それらのオブジェクトを取得する方法はいくつかあります。
mahboudz 2009

あなたはビューについていくらか正しいですが、その親について知っています。それは非常に明確な設計決定ではありませんが、スーパービューメンバー変数を直接使用していくつかのアクションを実行することがすでに確立されています(親タイプをチェック、親から削除など)。最近PureMVCを使用していて、デザインの抽象化についてもう少し厳格になりました:) iPhoneのUIViewクラスとUIViewControllerクラスとPureMVCのViewクラスとMediatorクラスを並行して作成します-ほとんどの場合、Viewクラスは必要ありませんMVCハンドラー/インターフェース(UIViewController / Mediator)について知っている。
Dimitar Dimitrov

9
キーワード:「ほとんど」。
Glenn Maynard

278

UIResponderドキュメントからnextResponder

UIResponderクラスは、次のレスポンダーを自動的に保存または設定せず、代わりにデフォルトでnilを返します。次のレスポンダを設定するには、サブクラスでこのメソッドをオーバーライドする必要があります。UIViewは、それを管理するUIViewControllerオブジェクト(ある場合)またはそのスーパービュー(ない場合)を返すことにより、このメソッドを実装します。UIViewControllerは、ビューのスーパービューを返すことでメソッドを実装します。UIWindowはアプリケーションオブジェクトを返し、UIApplicationはnilを返します。

したがって、ビューのnextResponderタイプがになるまでビューを再帰する場合UIViewController、ビューの親のviewControllerがあります。

まだ親View Controller がない場合があることに注意してください。ただし、ビューがviewControllerのビューのビュー階層の一部でない場合のみ。

Swift 3およびSwift 4.1拡張機能:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder?.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Swift 2拡張:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.nextResponder()
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Objective-Cカテゴリ:

@interface UIView (mxcl)
- (UIViewController *)parentViewController;
@end

@implementation UIView (mxcl)
- (UIViewController *)parentViewController {
    UIResponder *responder = self;
    while ([responder isKindOfClass:[UIView class]])
        responder = [responder nextResponder];
    return (UIViewController *)responder;
}
@end

このマクロは、カテゴリー汚染を回避します。

#define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
        __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
})

ただ1つのこと:カテゴリ汚染を心配する場合は、マクロではなく静的関数として定義してください。また、残忍な型キャストは危険であり、その上、マクロは正しくないかもしれませんが、よくわかりません。
mojuba 2015年

マクロは機能しますが、私は個人的にマクロバージョンのみを使用しています。私は多くのプロジェクトを開始し、これらのマクロユーティリティ関数の束をどこにでもドロップできるヘッダーを持っています。時間を節約できます。マクロが気に入らない場合は、マクロを関数に適用できますが、使用するすべてのファイルにマクロを配置する必要があるため、静的関数は面倒です。代わりに、非静的関数をヘッダーで宣言し、.mのどこかで定義したいですか?
mxcl 2015

一部のデリゲートまたは通知を避けてください。ありがとう!
Ferran Maylinch 2015年

UIResponder;)の拡張にする必要があります。非常に教育的な投稿。
ScottyBlades

32

@andreyが1行で回答します(Swift 4.1でテスト済み):

extension UIResponder {
    public var parentViewController: UIViewController? {
        return next as? UIViewController ?? next?.parentViewController
    }
}

使用法:

 let vc: UIViewController = view.parentViewController

parentViewControllerpublic拡張子が同じファイルにある場合は定義できません。UIView設定できますがfileprivate、コンパイルされますが機能しません。😐

正常に動作しています。これを、共通ヘッダー.xibファイル内の戻るボタンアクションに使用しました。
McDonal_11

23

デバッグ目的でのみ、_viewDelegateビューを呼び出してビューコントローラーを取得できます。これはプライベートAPIであるため、App Storeには安全ではありませんが、デバッグには役立ちます。

その他の便利な方法:

  • _viewControllerForAncestor-スーパービューチェーンのビューを管理する最初のコントローラーを取得します。(n00neimp0rtantに感謝)
  • _rootAncestorViewController -ビュー階層が現在ウィンドウに設定されている祖先コントローラーを取得します。

これがまさに私がこの質問に来た理由です。どうやら、「nextResponder」は同じことを行いますが、この回答が提供する洞察に感謝します。私はMVCを理解して気に入っていますが、デバッグは別の動物です!
mbm29414 14

4
これはビューコントローラーのメインビューでのみ機能し、そのサブビューでは機能しないようです。_viewControllerForAncestorビューコントローラーに属する最初のスーパービューが見つかるまで、スーパービューをたどります。
n00neimp0rtant 2015年

ありがとう@ n00neimp0rtant!私はこの回答を賛成して、人々があなたのコメントを見られるようにします。
eyuelt

より多くの方法で回答を更新します。
レオナタン

1
これはデバッグ時に役立ちます。
エヴァンチン、2015

10

UIViewを持つUIViewControllerへの参照を取得するには、UIResponder(UIViewとUIViewControllerのスーパークラス)の拡張を作成します。これにより、レスポンダーチェーンを上に移動し、UIViewController(それ以外の場合はnilを返す)に到達できます。

extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.nextResponder() is UIViewController {
            return self.nextResponder() as? UIViewController
        } else {
            if self.nextResponder() != nil {
                return (self.nextResponder()!).getParentViewController()
            }
            else {return nil}
        }
    }
}

//Swift 3
extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.next is UIViewController {
            return self.next as? UIViewController
        } else {
            if self.next != nil {
                return (self.next!).getParentViewController()
            }
            else {return nil}
        }
    }
}

let vc = UIViewController()
let view = UIView()
vc.view.addSubview(view)
view.getParentViewController() //provide reference to vc

4

Swift 3の高速で一般的な方法:

extension UIResponder {
    func parentController<T: UIViewController>(of type: T.Type) -> T? {
        guard let next = self.next else {
            return nil
        }
        return (next as? T) ?? next.parentController(of: T.self)
    }
}

//Use:
class MyView: UIView {
    ...
    let parentController = self.parentController(of: MyViewController.self)
}

2

コードに精通しておらず、特定のビューに対応するViewControllerを見つけたい場合は、以下を試すことができます。

  1. デバッグでアプリを実行する
  2. 画面に移動
  3. ビューインスペクターを開始する
  4. 検索したいビュー(または子ビューの方が良い)をつかむ
  5. 右側のペインからアドレスを取得します(例:0x7fe523bd3000)
  6. デバッグコンソールでコマンドの書き込みを開始します。
    po(UIView *)0x7fe523bd3000
    po [(UIView *)0x7fe523bd3000 nextResponder]
    po [[(UIView *)0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *)0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

ほとんどの場合、UIViewを取得しますが、時々UIViewControllerベースのクラスが存在します。


1

タップをView Controllerに伝播してそれを処理させることができると思います。これはより許容できるアプローチです。ビューからビューコントローラーにアクセスする場合は、他に方法がないため、ビューコントローラーへの参照を維持する必要があります。このスレッドを参照してください、それが役立つかもしれません: ビューからビューコントローラーにアクセスする


少数のビューがあり、すべてのビューを閉じ、viewWillDisappearを呼び出す必要がある場合、そのビューでタップを検出する方が、タップをビューコントローラーに渡してビューコントローラーをチェックするよりも簡単でしょう。どのビューがタップされたかを確認するためにすべてのビューで?
mahboudz 2009

0

Swift 3.0のよりタイプセーフなコード

extension UIResponder {
    func owningViewController() -> UIViewController? {
        var nextResponser = self
        while let next = nextResponser.next {
            nextResponser = next
            if let vc = nextResponser as? UIViewController {
                return vc
            }
        }
        return nil
    }
}

0

悲しいかな、ビューをシーンに追加すると、ビューをサブクラス化し、その中にビューコントローラー参照を格納するインスタンスプロパティなどを提供しない限り、これは不可能です...

ほとんどの場合-ほとんどのビューコントローラーは、ViewControllerのビューにサブビューを追加する責任を負ったプログラマーにとってよく知られているエンティティであるため、この投稿の元の問題を回避するのは非常に簡単です。そのプロパティを追加する気になりませんでした。


0

少し遅れますが、ViewControllerを含むあらゆるタイプのレスポンダを検索できるようにする拡張機能があります。

extension NSObject{
func findNext(type: AnyClass) -> Any{
    var resp = self as! UIResponder

    while !resp.isKind(of: type.self) && resp.next != nil
    {
        resp = resp.next!
    }

    return resp
  }                       
}

-1

ブレークポイントを設定した場合、これをデバッガーに貼り付けて、ビュー階層を印刷できます。

po [[UIWindow keyWindow] recursiveDescription]

その混乱のどこかにビューの親を見つけることができるはずです:)


recursiveDescriptionビューコントローラーではなく、ビュー階層のみを出力します
アランZeino、2014年

1
それから、ビューコントローラ@AlanZeinoを識別できます
Akshay
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.