回答:
これは長い間受け入れられてきた答えなので、より良い答えで修正する必要があると感じています。
必要性に関するいくつかのコメント:
これを実装する方法の例を次に示します。
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
ビューはデリゲートとインターフェイスし(UITableView
たとえば、そうです)、ビューコントローラーに実装されているか、最終的に使用する他のクラスに実装されているかは関係ありません。
私の元の答えは次のとおりです:私はこれをお勧めしません。ViewControllerへの直接アクセスが達成される残りの答えもお勧めしません
それを行うための組み込みの方法はありません。にを追加しIBOutlet
てUIView
これらをInterface Builderで接続することで回避できますが、これはお勧めできません。ビューは、ビューコントローラについて認識してはなりません。代わりに、@ Phil Mが提案するようにして、デリゲートとして使用するプロトコルを作成する必要があります。
Brockによって投稿された例を使用して、UIViewControllerではなくUIViewのカテゴリになるように変更し、再帰的にして、すべてのサブビューが親UIViewControllerを(うまくいけば)見つけることができるようにしました。
@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
- (id) traverseResponderChainForUIViewController;
@end
@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
// convenience function for casting and to "mask" the recursive function
return (UIViewController *)[self traverseResponderChainForUIViewController];
}
- (id) traverseResponderChainForUIViewController {
id nextResponder = [self nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
return nextResponder;
} else if ([nextResponder isKindOfClass:[UIView class]]) {
return [nextResponder traverseResponderChainForUIViewController];
} else {
return nil;
}
}
@end
このコードを使用するには、コードを新しいクラスファイル(私は「UIKitCategories」と名付けました)に追加し、クラスデータを削除します... @interfaceをヘッダーにコピーし、@ implementationを.mファイルにコピーします。次に、プロジェクトで#import "UIKitCategories.h"を使用して、UIViewコード内で使用します。
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView
のサブクラスですUIResponder
。UIResponder
は、-nextResponder
を返す実装でメソッドをレイアウトしますnil
。UIView
で説明されているようにUIResponder
(何らかの理由でUIView
)で説明されているように、このメソッドをオーバーライドします-nextResponder
。ビューにビューコントローラがある場合、によって返されます。ビューコントローラがない場合、メソッドはスーパービューを返します。
これをプロジェクトに追加すると、ロールする準備が整います。
@interface UIView (APIFix)
- (UIViewController *)viewController;
@end
@implementation UIView (APIFix)
- (UIViewController *)viewController {
if ([self.nextResponder isKindOfClass:UIViewController.class])
return (UIViewController *)self.nextResponder;
else
return nil;
}
@end
これでUIView
、ビューコントローラを返すための実用的なメソッドができました。
UIView
との間のレスポンダーチェーンに何もない場合にのみ機能しUIViewController
ます。Phil Mの再帰をフィーチャーした答えは、進むべき道です。
UIViewにカテゴリを追加する必要なく、レスポンダーチェーン全体をトラバースするためのより軽量なアプローチを提案します。
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
すでに与えられたいくつかの答えを組み合わせて、私はそれも私の実装に同梱しています:
@implementation UIView (AppNameAdditions)
- (UIViewController *)appName_viewController {
/// Finds the view's view controller.
// Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
Class vcc = [UIViewController class];
// Traverse responder chain. Return first found view controller, which will be the view's view controller.
UIResponder *responder = self;
while ((responder = [responder nextResponder]))
if ([responder isKindOfClass: vcc])
return (UIViewController *)responder;
// If the view controller isn't found, return nil.
return nil;
}
@end
このカテゴリは、作成するすべてのアプリケーションに同梱されているARC対応の静的ライブラリの一部です。何度かテストされましたが、問題やリークは見つかりませんでした。
PS:関係するビューが自分のサブクラスである場合は、私が行ったようなカテゴリを使用する必要はありません。後者の場合、メソッドをサブクラスに配置するだけで問題ありません。
ビュー、ボタン、ラベルなどを渡して親を取得できるように、de answer を変更しましたUIViewController
。これが私のコードです。
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Swift 3バージョンの編集
class func viewController(_ view: UIView) -> UIViewController {
var responder: UIResponder? = view
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
編集2:-スイフト拡張
extension UIView
{
//Get Parent View Controller from any view
func parentViewController() -> UIViewController {
var responder: UIResponder? = self
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
}
ビューがサブビューであるウィンドウのルートビューコントローラーにアクセスできることを忘れないでください。そこから、たとえばナビゲーションビューコントローラを使用していて、それに新しいビューをプッシュしたい場合は、次のようにします。
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
ただし、最初にウィンドウのrootViewControllerプロパティを適切に設定する必要があります。最初にコントローラーを作成するときにこれを行います(例:アプリのデリゲート):
-(void) applicationDidFinishLaunching:(UIApplication *)application {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
RootViewController *controller = [[YourRootViewController] alloc] init];
[window setRootViewController: controller];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[controller release];
[window addSubview:[[self navigationController] view]];
[window makeKeyAndVisible];
}
[[self navigationController] view]
、ウィンドウの「メイン」(サブ)ビューなので、ウィンドウのrootViewController
プロパティはnavigationController
、「メイン」ビューをすぐに制御するように設定する必要があるようです。
Ushoxを含め、これらの答えは技術的には正しいですが、承認された方法は、新しいプロトコルを実装するか、既存のプロトコルを再利用することだと思います。プロトコルは、オブザーバーを観察されたものから隔離します。それらの間にメールスロットを置くようなものです。実際には、GabrielがpushViewControllerメソッドの呼び出しを介して行うことです。viewControllerはnavigationControllerプロトコルに準拠しているため、viewは、navigationControllerにビューをプッシュするよう丁寧に要求することが適切なプロトコルであることを「認識」しています。独自のプロトコルを作成することもできますが、Gabrielの例を使用してUINavigationControllerプロトコルを再利用するだけで十分です。
再利用したい小さなコンポーネントがある状況に遭遇し、再利用可能なビュー自体にいくつかのコードを追加しました(実際にはを開くボタンにすぎませんPopoverController
)。
これはiPadで正常に機能しUIPopoverController
ますが(存在するため、への参照は必要ありませんUIViewController
)、同じコードが機能することは、presentViewController
からを突然参照することを意味しますUIViewController
。ちょっと矛盾していますよね?
前述のように、UIViewにロジックを含めることは最善の方法ではありません。しかし、必要な数行のコードを別のコントローラーでラップするのは本当に役に立たないと感じました。
いずれの場合も、UIViewに新しいプロパティを追加する迅速なソリューションを次に示します。
extension UIView {
var viewController: UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let responder = responder as? UIViewController {
return responder
}
responder = responder?.nextResponder()
}
return nil
}
}
場合によっては、ビューコントローラが誰であるかを見つけるのは「悪い」考えではないと思います。悪い考えかもしれないのは、スーパービューが変更されるのと同じように変更される可能性があるため、このコントローラーへの参照を保存することです。私の場合、レスポンダーチェーンを通過するゲッターがあります。
//.h
@property (nonatomic, readonly) UIViewController * viewController;
//.m
- (UIViewController *)viewController
{
for (UIResponder * nextResponder = self.nextResponder;
nextResponder;
nextResponder = nextResponder.nextResponder)
{
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController *)nextResponder;
}
// Not found
NSLog(@"%@ doesn't seem to have a viewController". self);
return nil;
}
viewControllerを見つけるための最も単純なwhileループ。
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(他の回答よりも簡潔です)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
私は最初のビューにアクセスする必要のある私のユースケースUIViewController
私は周りにラップするオブジェクトを持っている:AVPlayer
/ AVPlayerViewController
と私は、単純な提供したいshow(in view: UIView)
埋め込みます方法AVPlayerViewController
にしますview
。そのために、私がアクセスする必要view
のUIViewController
。
これは質問に直接回答するのではなく、質問の意図についての仮定を行います。
ビューがあり、そのビューでビューコントローラなどの別のオブジェクトのメソッドを呼び出す必要がある場合は、代わりにNSNotificationCenterを使用できます。
まず、ヘッダーファイルに通知文字列を作成します
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
ビュー呼び出しでpostNotificationName:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
次に、ビューコントローラーでオブザーバーを追加します。私はviewDidLoadでこれを行います
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
これで(同じビューコントローラでも)、上記の@selectorで示したように、メソッドcopyString:を実装します。
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
これが正しい方法だと言っているわけではありません。最初のレスポンダーチェーンを実行するよりもクリーンなようです。このコードを使用して、UITableViewにUIMenuControllerを実装し、イベントをUIViewControllerに渡して、データで何かを実行できるようにしました。
それは確かに悪い考えであり、間違った設計ですが、@ Phil_Mによって提案された最良の回答のSwiftソリューションをすべて楽しむことができると確信しています。
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.nextResponder() {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder)
}
モーダルダイアログを表示したりデータを追跡したりするなど、単純なことを行うことが目的である場合、それはプロトコルの使用を正当化しません。私は個人的にこの関数をユーティリティオブジェクトに格納しています。UIResponderプロトコルを実装するあらゆるものから使用できます。
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
@Phil_Mへのすべてのクレジット
Swiftierソリューション
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
sequence
ビット)の優れたデモンストレーションがあります。したがって、「迅速」が「より機能的」であることを意味する場合、私はそれがより迅速であることを推測します。
Swift 4の更新バージョン:@Phil_Mと@ paul-slmに感謝
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.next {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(responder: nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder: responder)
}
Swift 4バージョン
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
}
使用例
if let parent = self.view.parentViewController{
}
return
キーワードは不要になりました🤓解決策1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
解決策2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
私のソリューションはおそらく一種の偽物と見なされますが、mayoneezと同様の状況があり(EAGLViewのジェスチャーに応答してビューを切り替えたい)、次のようにEAGLのビューコントローラーを取得しました。
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
。
ClassName *object
では、アスタリスクを使用して、作成するオブジェクトを宣言します。
観察者が観察者に知らせる必要がある場合があると思います。
UIViewControllerのUIViewが状況に応答しており、最初に親ビューコントローラーに戻るボタンを非表示にするように指示し、完了時に親ビューコントローラーにスタックからそれ自体をポップする必要があることを伝える必要がある同様の問題を確認しました。
私は成功を収めていない代表者たちとこれを試みてきました。
これが悪い考えである理由がわかりませんか?
これをApp Storeにアップロードしない場合は、UIViewのプライベートメソッドを使用することもできます。
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
rootViewControllerが、AppDelegateクラスで設定されたUINavigationViewControllerである場合、
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
ここで、cにはビューコントローラクラスが必要です。
使用法:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
道はない。
UIViewControllerポインターをUIView(または適切な継承)に渡します。私はIBを信じていないので、問題に対するIBのアプローチを手伝うことができないのは残念です。
最初のコメンターに回答するには:何ができるかが決まるため、だれがあなたに電話したかを知る必要がある場合があります。たとえば、データベースの場合、読み取りアクセスのみまたは読み取り/書き込み...