web-dev-qa-db-ja.com

ビューを指定すると、そのviewControllerを取得するにはどうすればよいですか?

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

123
mahboudz

はい、superviewはビューを含むビューです。ビューは、MVCの原則を破るので、ビューコントローラーを正確に知る必要はありません。

一方、コントローラーは、どのビューに責任があるか(self.view = myView)を知っており、通常、このビューはコントローラーに処理するためのメソッド/イベントを委任します。

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

39

UIRespondernextResponderドキュメントから:

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

したがって、ビューのnextResponderUIViewController型になるまで再帰する場合、ビューの親viewControllerがあります。

ただし、親View Controllerがnotある可能性があります。ただし、ビューに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; \
})
253
mxcl

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

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

使用法:

 let vc: UIViewController = view.parentViewController
22

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

その他の便利な方法:

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

UIViewを持つUIViewControllerへの参照を取得するには、UIResponder(UIViewおよびUIViewControllerのスーパークラス)の拡張を作成します。

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
9
Andrey

Swift 3の高速で汎用的な方法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)
}
4
iTSangar

コードに精通しておらず、特定のビューに対応する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
m4js7er

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

0
Nava Carmon

少し遅れましたが、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
  }                       
}
0
Hackbarth

残念ながら、これは、ビューをサブクラス化して、インスタンスプロパティなどを提供しない限り不可能です。ビューがシーンに追加されると、内部にView Controller参照を保存します。

ほとんどの場合-ほとんどのView Controllerは、ViewControllerのViewへのサブビューの追加を担当したプログラマーによく知られたエンティティであるため、この投稿の元の問題を回避することは非常に簡単です;-)Appleは、そのプロパティを追加することを気にしませんでした。

0
Morten Carlsen

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
Denis Krivitski