IT

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

lottoking 2020. 7. 7. 07:27
반응형

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


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


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

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

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


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

디버그 목적으로 만 _viewDelegate뷰를 호출 하여 뷰 컨트롤러를 가져올 수 있습니다. 이것은 개인 API이므로 App Store에는 안전하지 않지만 디버깅에는 유용합니다.

다른 유용한 방법 :

  • _viewControllerForAncestor-슈퍼 뷰 체인에서 뷰를 관리하는 첫 번째 컨트롤러를 가져옵니다. (감사합니다 n00neimp0rtant)
  • _rootAncestorViewController -현재 창에 뷰 계층이 설정된 조상 컨트롤러를 가져옵니다.

@andrey 는 한 줄로 답변합니다 ( Swift 4.1 에서 테스트 됨 ).

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

용법:

 let vc: UIViewController = view.parentViewController

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

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

If you are not familiar with the code and you want to find ViewController coresponding to given view, then you can try:

  1. Run app in debug
  2. Navigate to screen
  3. Start View inspector
  4. Grab the View you want to find (or a child view even better)
  5. From the right pane get the address (e.g. 0x7fe523bd3000)
  6. In debug console start writing commands:
    po (UIView *)0x7fe523bd3000
    po [(UIView *)0x7fe523bd3000 nextResponder]
    po [[(UIView *)0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *)0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

In most cases you will get UIView, but from time to time there will be UIViewController based class.


I think you can propagate the tap to the view controller and let it handle it. This is more acceptable approach. As for accessing a view controller from its view, you should maintain a reference to a view controller, since there is no another way. See this thread, it might help: Accessing view controller from a view


More type safe code for 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
    }
}

Alas, this is impossible unless you subclass the view and provide it with an instance property or alike which stores the view controller reference inside it once the view is added to the scene...

In most cases - it is very easy to work around the original problem of this post as most view controllers are well-known entities to the programmer who was responsible for adding any subviews to the ViewController's View ;-) Which is why I guess that Apple never bothered to add that property.


A little bit late, but here's an extension that enable you to find a responder of any type, including 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
  }                       
}

If you set a breakpoint, you can paste this into the debugger to print the view hierarchy:

po [[UIWindow keyWindow] recursiveDescription]

You should be able to find your view's parent somewhere in that mess :)

참고URL : https://stackoverflow.com/questions/1372977/given-a-view-how-do-i-get-its-viewcontroller

반응형