답변:
이것이 오랫동안 받아 들여진 대답이므로 더 나은 대답으로 수정해야한다고 생각합니다.
필요성에 대한 의견 :
구현 방법의 예는 다음과 같습니다.
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
뷰는 델리게이트와 인터페이스하고 ( UITableView
예를 들어) 뷰 컨트롤러 또는 다른 클래스에서 구현되었는지는 중요하지 않습니다.
내 원래 답변은 다음과 같습니다. 권장하지 않습니다. 뷰 컨트롤러에 직접 액세스 할 수있는 나머지 답변도 없습니다.
기본 제공 방법이 없습니다. 당신이를 추가하여 주위를 얻을 수 있지만 IBOutlet
상의 UIView
와 인터페이스 빌더에서 이러한 연결이 권장되지 않습니다. 뷰는 뷰 컨트롤러에 대해 알 수 없습니다. 대신 @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
이 코드를 사용하려면 새 클래스 파일 (내 이름은 "UIKit 카테고리")에 추가하고 클래스 데이터를 제거하십시오. @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 지원의 일부입니다 내가 만드는 모든 응용 프로그램에서 제공하는 정적 라이브러리의 . 여러 번 테스트를 받았으며 문제 나 누수를 찾지 못했습니다.
추신 : 관련보기가 귀하의 하위 클래스 인 경우처럼 카테고리를 사용할 필요가 없습니다. 후자의 경우 메소드를 서브 클래스에 넣으면됩니다.
나는 수정 드 내가 어떤보기 버튼을 통과 그것의 부모를 얻을 수 등의 레이블을 지정할 수 있도록 답을 UIViewController
. 여기 내 코드가 있습니다.
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
스위프트 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];
}
Ushox를 포함한 이러한 답변은 기술적으로 정확하지만 승인 된 방법은 새로운 프로토콜을 구현하거나 기존 프로토콜을 재사용하는 것입니다. 프로토콜은 관찰자와 관찰자를 격리 시키며, 그 사이에 메일 슬롯을 넣는 것과 같습니다. 실제로, 그것은 pushViewController 메소드 호출을 통해 Gabriel이하는 일입니다. viewController는 navigationController 프로토콜을 준수하기 때문에, view는 navigationController에게 예의 바르게 푸시를 요청하는 것이 적절한 프로토콜임을 "알고있다". 가브리엘의 예제를 사용하고 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;
//.미디엄
- (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"
viewcall 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의 모든 크레딧
스위프 티어 솔루션
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
비트)에 대한 훌륭한 데모가 제공 됩니다. "swifty"가 "더 기능적"을 의미한다면 더 빠르다고 생각합니다.
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)
}
스위프트 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 접근 방식을 도울 수 없어서 죄송합니다.
첫 번째 해설자에게 답하기 : 때때로 당신이 할 수있는 일을 결정하기 때문에 누가 당신을 불렀는지 알아야합니다. 예를 들어 데이터베이스를 사용하면 읽기 권한 만 있거나 읽기 / 쓰기가 가능할 수 있습니다.