뷰가 주어지면 어떻게 viewController를 얻습니까?


141

에 대한 포인터가 있습니다 UIView. 어떻게 액세스 UIViewController합니까? [self superview]또 다른 UIView것은 아니지만 그렇지 UIViewController않습니까?



기본적으로 뷰가 닫힐 때 viewController의 viewWillAppear를 호출하려고합니다. 탭을 감지하고 [self removeFromSuperview];를 호출하는 뷰 자체에 의해 뷰가 닫힙니다. viewController가 viewWillAppear / WillDisappear / DidAppear / DidDisappear 자체를 호출하지 않습니다.
mahboudz

뷰가 닫힐 때 viewWillDisappear를 호출하려고했습니다.
mahboudz

답변:


42

예, superview 보기가 포함 된보기입니다. 뷰 컨트롤러가 MVC 원칙을 위반하기 때문에 뷰 컨트롤러가 정확히 무엇인지 알 수 없습니다.

반면에 컨트롤러는 어떤보기를 담당하는지 알고 있습니다 (self.view = myView )를 일반적으로이 뷰는 컨트롤러에 처리 할 메소드 / 이벤트를 위임합니다.

일반적으로 뷰에 대한 포인터 대신 컨트롤러에 대한 포인터가 있어야 제어 컨트롤러를 실행하거나 뷰에 무언가를 전달할 수 있습니다.


24
그것이 MVC 원칙을 어길 것인지 확실하지 않습니다. 어느 시점에서든 하나의보기에는 하나의보기 컨트롤러 만 있습니다. 메시지를 다시 전달하기 위해 메시지를 전달할 수 있으려면 자동 추적 기능을 사용해야합니다 (추적을 유지하기 위해 속성 추가). 한 견해에 대해 똑같은 말을 할 수 있습니다. 왜 당신이 어떤 아이인지 알아야합니까? 또는 다른 형제 자매보기가 있는지 여부. 그러나 이러한 객체를 얻는 방법이 있습니다.
mahboudz

부모에 대해 알면서도 뷰에 대해 다소 옳습니다. 설계 결정은 명확하지 않지만 이미 슈퍼 뷰 멤버 변수 (부모 유형 확인, 부모에서 제거 등)를 사용하여 일부 작업을 수행하기 위해 이미 설정되었습니다. 최근 PureMVC와 함께 일하면서 디자인 추상화에 대해 조금 까다로워졌습니다. :) iPhone의 UIView 및 UIViewController 클래스와 PureMVC의 View 및 Mediator 클래스를 병렬로 만들려고합니다. 대부분의 경우 View 클래스는 필요하지 않습니다. MVC 핸들러 / 인터페이스 (UIViewController / Mediator)에 대해 알고 있어야합니다.
Dimitar Dimitrov

9
핵심어 : "most".
Glenn Maynard,

278

UIResponder설명서에서 nextResponder:

UIResponder 클래스는 다음 응답자를 자동으로 저장하거나 설정하지 않고 기본적으로 nil을 반환합니다. 다음 응답자를 설정하려면 서브 클래스가이 메소드를 대체해야합니다. UIView는 관리하는 UIViewController 객체 (있는 경우) 또는 superview (없는 경우)를 반환하여이 메서드를 구현합니다 . UIViewController는 뷰의 슈퍼 뷰를 반환하여 메소드를 구현합니다. UIWindow는 응용 프로그램 객체를 반환하고 UIApplication은 nil을 반환합니다.

따라서보기 nextResponder유형이 될 때까지 다시 보기를 반복하면UIViewController 뷰의 부모 viewController가 있습니다.

여전히 그렇지 않을 수 있습니다 상위 뷰 컨트롤러 . 그러나 뷰가 viewController의 뷰 계층 구조의 일부가 아닌 경우에만 가능합니다.

스위프트 3 및 스위프트 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
    }
}

스위프트 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
    }
}

목표 -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; \
})

한 가지 : 범주 오염에 대해 걱정할 경우 매크로가 아닌 정적 함수로 정의하십시오. 또한 잔인한 타입 캐스트는 위험하며 그 위에 매크로가 정확하지 않을 수도 있지만 확실하지 않습니다.
mojuba

매크로가 작동하며 개인적으로 매크로 버전 만 사용합니다. 나는 많은 프로젝트를 시작하고 이러한 매크로 유틸리티 기능을 사용하여 어디에서나 머리글을 넣을 수 있습니다. 시간을 절약 할 수 있습니다. 매크로가 마음에 들지 않으면 매크로를 함수에 적용 할 수 있지만 정적 함수는 지루한 것처럼 보입니다. 사용하려는 모든 파일에 하나씩 넣어야하기 때문입니다. 대신 정적이 아닌 함수를 헤더에 선언하고 .m에 정의하고 싶습니까?
mxcl

일부 대리인 또는 알림을 피하는 것이 좋습니다. 감사!
Ferran Maylinch 2016 년

확장명을 UIResponder;)로 만들어야합니다 . 매우 교묘 한 게시물.
ScottyBlades

32

@andrey 는 한 줄로 답변합니다 ( 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

4
이것은 뷰 컨트롤러의 기본 뷰에서만 작동하며 하위 뷰에서는 작동하지 않습니다. _viewControllerForAncestor뷰 컨트롤러에 속하는 첫 번째 뷰를 찾을 때까지 수퍼 뷰를 순회합니다.
n00neimp0rtant

감사합니다 @ n00neimp0rtant! 사람들이 당신의 의견을 볼 수 있도록이 답변을지지하고 있습니다.
eyuelt

더 많은 방법으로 답변을 업데이트하십시오.
레오 나탄

1
디버깅 할 때 도움이됩니다.
evanchin

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

스위프트 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 next 응답자]
    po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
    po [[[((UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

대부분의 경우 UIView를 얻지 만 때때로 UIViewController 기반 클래스가 있습니다.


1

탭을 뷰 컨트롤러에 전파하여 처리 할 수 ​​있다고 생각합니다. 이것은 더 수용 가능한 접근 방식입니다. 뷰에서 뷰 컨트롤러에 액세스하는 경우 다른 방법이 없으므로 뷰 컨트롤러에 대한 참조를 유지해야합니다. 이 스레드를 참조하십시오. 보기에서보기 컨트롤러에 액세스


소수의 뷰가 있고 모든 뷰를 닫고 viewWillDisappear를 호출해야하는 경우 해당 뷰에서 탭을 뷰 컨트롤러에 전달하고 뷰 컨트롤러를 확인하는 것보다 탭을 감지하는 것이 쉽지 않을 것입니다 어떤 견해를 봤는지 모든 견해와 함께?
mahboudz

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단지 인쇄 컨트롤러를 볼 수 없습니다, 계층 구조를.
Alan Zeino

1
여기에서 viewcontroller를 식별 할 수 있습니다 @AlanZeino
Akshay
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.