개인 API를 사용하지 않고 현재 첫 번째 응답자 확보


340

나는 일주일 전에 내 앱을 제출했고 오늘 두려운 거부 이메일을 받았습니다. 비공개 API를 사용하고 있기 때문에 내 앱을 수락 할 수 없다고 알려줍니다. 구체적으로 말하면

응용 프로그램에 포함 된 비공개 API는 firstResponder입니다.

이제 문제가되는 API 호출은 실제로 여기에서 찾은 솔루션입니다.

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView   *firstResponder = [keyWindow performSelector:@selector(firstResponder)];

화면에서 현재 첫 번째 응답자를 얻으려면 어떻게해야합니까? 내 앱이 거부되지 않는 방법을 찾고 있습니다.


5
뷰를 반복하는 대신 이처럼 멋진 마법을 사용할 수 있습니다 . stackoverflow.com/a/14135456/746890
Chris Nolet

답변:


338

내 응용 프로그램 중 하나에서 사용자가 배경을 탭하면 첫 번째 응답자가 사직하기를 원합니다. 이를 위해 UIView에 카테고리를 작성했는데 UIWindow를 호출합니다.

다음은이를 기반으로하며 첫 번째 응답자를 반환해야합니다.

@implementation UIView (FindFirstResponder)
- (id)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;        
    }
    for (UIView *subView in self.subviews) {
        id responder = [subView findFirstResponder];
        if (responder) return responder;
    }
    return nil;
}
@end

iOS 7 이상

- (id)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;
    }
    for (UIView *subView in self.view.subviews) {
        if ([subView isFirstResponder]) {
            return subView;
        }
    }
    return nil;
}

빠른:

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }

        for subview in subviews {
            if let firstResponder = subview.firstResponder {
                return firstResponder
            }
        }

        return nil
    }
}

Swift의 사용 예 :

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}

278
왜 이것이 단순히 전화하는 것보다 더 나은 해결책 [self.view endEditing:YES]입니까?
Tim Sullivan

69
@Tim 나는 당신이 그렇게 할 수 없었습니다. 첫 번째 응답자를 사임하는 것이 훨씬 간단한 방법입니다. 그러나 최초의 응답자를 식별하는 것이 원래의 질문에 도움이되지 않습니다.
토마스 ül 러

17
또한, 이것은 첫 번째 응답자와 사임하는 것보다 다른 일을하고 싶을 수도 있기 때문에 더 나은 대답입니다.
Erik B

3
이는 첫 번째 응답자 (예 : UIViewController 또는 UIApplication) 일 수있는 다른 UIResponder가 아닌 UIView 만 찾습니다.
Patrick Pijnappel

10
iOS 7의 차이점은 무엇입니까? 이 경우에도 재귀를 원하지 않습니까?
Peter DeWeese

531

최종 목표가 첫 번째 응답자를 사임하는 것이라면 다음과 같이 작동합니다. [self.view endEditing:YES]


122
이것이 "질문"에 대한 답이라고 말하는 여러분 모두에게 실제 질문을 살펴볼 수 있습니까? 질문은 현재 첫 번째 응답자를 얻는 방법을 묻습니다. 첫 번째 응답자를 사임하는 방법이 아닙니다.
Justin Kredible

2
이 self.view endEditing을 어디로 불러야합니까?
user4951

8
이것이 정확히 질문에 대답하는 것은 아니지만 분명히 매우 유용한 대답입니다. 감사!
NovaJoe

6
질문에 대한 답변이 아니더라도 +1합니다. 왜? 많은 사람들이 마음에 질문을 여기에 서 있기 때문에 그 중 (순간)이 응답 답변 : 최소 267 ...
한국 Jarc

1
이것은 질문에 대답하지 않습니다.
Sasho

186

첫 번째 응답자를 조작하는 일반적인 방법은 목표가없는 작업을 사용하는 것입니다. 이것은 임의의 메시지를 응답자 체인에 보내고 (첫 번째 응답자부터 시작) 누군가 메시지에 응답 할 때까지 체인을 계속 진행하는 방법입니다 (선택자와 일치하는 방법을 구현했습니다).

키보드를 닫을 경우 가장 먼저 응답하는 창이나보기에 관계없이 가장 효과적인 방법입니다.

[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];

이것은 심지어보다 효과적이어야합니다 [self.view.window endEditing:YES].

( 개념을 상기시켜 준 BigZaphod 에게 감사한다 )


4
너무 멋져. 아마 내가 아직 본 가장 코코아 터치 트릭입니다. 끝이 없어요!
mluisbrown

이것은 최고의 솔루션입니다, 백만 감사합니다! (이 경우에 우리의 뷰 계층의 일부가 아닌) 키보드의 액세서리보기의 일부는 심지어 활성 텍스트 필드의 작동하기 때문에이 더 나은이 하나 위의 솔루션보다 방법입니다
Pizzaiola 고르곤 졸라

2
이 솔루션은 모범 사례 솔루션입니다. 시스템은 이미 첫 번째 응답자가 누구인지 알고 있으므로 찾을 필요가 없습니다.
leftspin

2
이 답변에 둘 이상의 공감대를 제공하고 싶습니다. 이것은 천재적이다.
리드 메인

1
devios1 : Jakob의 대답은 질문에서 요구하는 것과 정확히 일치하지만, 응답자 시스템을 설계된 방식으로 사용하는 것이 아니라 응답자 시스템의 해킹입니다. 이것은 더 깨끗하고 대부분의 사람들 이이 질문에 대해 한 줄로도 달성합니다. 물론 reResponderResponder뿐만 아니라 대상 액션 스타일 메시지도 보낼 수 있습니다.
nevyn

98

다음을 호출하여 첫 ​​번째 응답자를 빠르게 찾을 수있는 카테고리가 있습니다 [UIResponder currentFirstResponder]. 다음 두 파일을 프로젝트에 추가하십시오.

UIResponder + FirstResponder.h

#import <Cocoa/Cocoa.h>
@interface UIResponder (FirstResponder)
    +(id)currentFirstResponder;
@end

UIResponder + FirstResponder.m

#import "UIResponder+FirstResponder.h"
static __weak id currentFirstResponder;
@implementation UIResponder (FirstResponder)
    +(id)currentFirstResponder {
         currentFirstResponder = nil;
         [[UIApplication sharedApplication] sendAction:@selector(findFirstResponder:) to:nil from:nil forEvent:nil];
         return currentFirstResponder;
    }
    -(void)findFirstResponder:(id)sender {
        currentFirstResponder = self;
    }
@end

여기서 비결은 작업을 nil로 보내면 첫 번째 응답자에게 보냅니다.

(나는 원래이 답변을 https://stackoverflow.com/a/14135456/322427 )에 게시했습니다.


1
+1 이것은 내가 보았던 문제 (및 실제 질문)에 대한 가장 우아한 해결책입니다. 재사용 가능하고 효율적입니다! 이 답변을 찬성하십시오!
devios1

3
브라보. 이것은 내가 Objc에게 본 것 중 가장 아름답고 깨끗한 해킹 일 수 있습니다.
Aviel Gross

아주 좋아요 체인에있는 누군가 (있는 경우)가 특정 조치 방법을 처리 할 첫 번째 응답자에게 요청할 수 있도록하려면 실제로 잘 작동해야합니다. 이를 사용하여 처리 가능한지 여부에 따라 컨트롤을 활성화하거나 비활성화 할 수 있습니다. 나는 이것에 대해 내 자신의 질문 에 대답하고 당신의 대답을 참조 할 것입니다.
Benjohn


경고 : 여러 창을 추가하기 시작하면 작동하지 않습니다. 불행히도 아직 그 이상의 원인을 지적 할 수는 없습니다.
Heath Borders

41

다음은 Jakob Egger의 가장 훌륭한 답변을 기반으로 Swift에서 구현 된 확장입니다.

import UIKit

extension UIResponder {
    // Swift 1.2 finally supports static vars!. If you use 1.1 see: 
    // http://stackoverflow.com/a/24924535/385979
    private weak static var _currentFirstResponder: UIResponder? = nil

    public class func currentFirstResponder() -> UIResponder? {
        UIResponder._currentFirstResponder = nil
        UIApplication.sharedApplication().sendAction("findFirstResponder:", to: nil, from: nil, forEvent: nil)
        return UIResponder._currentFirstResponder
    }

    internal func findFirstResponder(sender: AnyObject) {
        UIResponder._currentFirstResponder = self
    }
}

스위프트 4

import UIKit    

extension UIResponder {
    private weak static var _currentFirstResponder: UIResponder? = nil

    public static var current: UIResponder? {
        UIResponder._currentFirstResponder = nil
        UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
        return UIResponder._currentFirstResponder
    }

    @objc internal func findFirstResponder(sender: AnyObject) {
        UIResponder._currentFirstResponder = self
    }
}

1
정적 var가 지원되는 swift1.2에 대한 수정 된 답변.
nacho4d

안녕하세요, viewDidAppear ()의 새로운 단일 뷰 응용 프로그램 에서이 결과를 인쇄하면 nil이 발생합니다. 견해가 첫 번째 응답자가되어서는 안됩니까?
farhadf

@LinusGeffarth current대신 정적 메소드를 호출하는 것이 더 합리적이지 first않습니까?
코드 사령관

맞춰보세요, 편집했습니다. 변수 이름을 약어로 사용할 때 중요한 부분을 삭제 한 것 같습니다.
LinusGeffarth

29

예쁘지는 않지만 응답자가 무엇인지 모르는 경우 firstResponder를 사임하는 방법은 다음과 같습니다.

IB 또는 프로그래밍 방식으로 UITextField를 만듭니다. 그것을 숨기십시오. IB에서 작성한 경우 코드와 연결하십시오.

그런 다음 키보드를 닫으려면 응답자를 보이지 않는 텍스트 필드로 전환하고 즉시 서명을 취소하십시오.

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];

61
이것은 동시에 끔찍하고 천재적입니다. 좋은.
Alex Wayne

4
애플은 언젠가 숨겨진 컨트롤을 첫 번째 응답자가되게하는 것이 의도 된 동작이 아니라고 판단하고 iOS가 더 이상이 작업을 수행하지 않도록 "고정"하면 트릭이 작동하지 않을 수 있다고 생각합니다.
Thomas Tempelmann

2
또는 firstResponder를 현재 표시된 UITextField에 할당 한 다음 해당 특정 textField에서 resignFirstResponder를 호출 할 수 있습니다. 이렇게하면 숨겨진보기를 만들 필요가 없습니다. 모두 동일 +1
Swifty McSwifterton 2016 년

3
차라리 그것이 단지 끔찍하다고 생각합니다 ... 키보드 레이아웃을 숨기 전에 키보드 레이아웃 (또는 입력 화면)을 바꿀 수 있습니다.
Daniel

19

nevyn의 답변 스위프트 3 & 4 버전 :

UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)

10

올바른 첫 번째 응답자를보고하는 솔루션이 있습니다 (다른 솔루션은 UIViewController 첫 번째 응답자로 하지 않음). 뷰 계층 구조를 반복 할 필요가 없으며 개인 API를 사용하지 않습니다.

Apple의 메소드 sendAction : to : from : forEvent를 활용합니다. 첫 번째 응답자에 액세스하는 방법을 이미 알고있는 합니다.

우리는 두 가지 방법으로 조정해야합니다.

  • UIResponder첫 번째 응답자에서 자체 코드를 실행할 수 있도록 확장 하십시오.
  • UIEvent첫 번째 응답자를 리턴하기위한 서브 클래스

코드는 다음과 같습니다.

@interface ABCFirstResponderEvent : UIEvent
@property (nonatomic, strong) UIResponder *firstResponder;
@end

@implementation ABCFirstResponderEvent
@end

@implementation UIResponder (ABCFirstResponder)
- (void)abc_findFirstResponder:(id)sender event:(ABCFirstResponderEvent *)event {
    event.firstResponder = self;
}
@end

@implementation ViewController

+ (UIResponder *)firstResponder {
    ABCFirstResponderEvent *event = [ABCFirstResponderEvent new];
    [[UIApplication sharedApplication] sendAction:@selector(abc_findFirstResponder:event:) to:nil from:nil forEvent:event];
    return event.firstResponder;
}

@end

7

첫 번째 응답자는 UIResponder 클래스의 모든 인스턴스가 될 수 있으므로 UIView에도 불구하고 첫 번째 응답자가 될 수있는 다른 클래스가 있습니다. 예를 들어 UIViewController첫 번째 응답자 일 수도 있습니다.

이 요점 에서는 응용 프로그램 창의 rootViewController에서 시작하여 컨트롤러 계층을 반복하여 첫 번째 응답자를 얻는 재귀적인 방법을 찾을 수 있습니다.

다음을 수행하여 첫 번째 응답자를 검색 할 수 있습니다.

- (void)foo
{
    // Get the first responder
    id firstResponder = [UIResponder firstResponder];

    // Do whatever you want
    [firstResponder resignFirstResponder];      
}

그러나 첫 번째 응답자가 UIView 또는 UIViewController의 하위 클래스가 아닌 경우이 방법은 실패합니다.

이 문제를 해결하기 UIResponder위해이 클래스의 모든 살아있는 인스턴스의 배열을 만들 수 있도록 범주를 생성하고 마법의 스위 즐링을 수행 하여 다른 접근 방식을 수행 할 수 있습니다. 그런 다음 첫 번째 응답자를 얻으려면 간단하게 반복하고 각 객체에 대해 묻습니다 -isFirstResponder.

이 접근법은 이 다른 요지 에서 구현 된 것을 찾을 수 있습니다 .

도움이 되길 바랍니다.


7

사용 스위프트 와 함께 특정하는 것은 UIView객체 이 힘의 도움을 :

func findFirstResponder(inView view: UIView) -> UIView? {
    for subView in view.subviews as! [UIView] {
        if subView.isFirstResponder() {
            return subView
        }

        if let recursiveSubView = self.findFirstResponder(inView: subView) {
            return recursiveSubView
        }
    }

    return nil
}

당신의 장소에 넣고 다음 UIViewController과 같이 사용하십시오 :

let firstResponder = self.findFirstResponder(inView: self.view)

결과는 선택적 값 이므로 지정된 뷰 하위보기에서 firstResponser를 찾을 수없는 경우에는 0이됩니다.


5

첫 번째 응답자가 될 수있는보기를 반복하고 현재보기 - (BOOL)isFirstResponder인지 판별하는 데 사용하십시오 .


4

Peter Steinberger 는 개인 알림에 대해 트윗했습니다UIWindowFirstResponderDidChangeNotification . 첫 번째 응답자 변경 사항을보고 싶은지 확인할 수 있습니다.


아마 좋은 생각이 될 수도 개인 알림을 사용하여 개인 API를 사용하기 때문에 그는 ...을 거부
라즐로

4

사용자가 배경 영역을 누를 때 키보드를 죽여야하는 경우 제스처 인식기를 추가하고 [[self view] endEditing:YES]메시지 를 보내는 데 사용하지 않는 이유는 무엇입니까?

xib 또는 스토리 보드 파일에 탭 동작 인식기를 추가하고 동작에 연결할 수 있습니다.

이것처럼 보이고 완성되었습니다

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}

이것은 나를 위해 잘 작동했습니다. Xib의보기와의 연결을 하나 선택했으며, 콘센트 컬렉션 참조에서 호출 할 추가보기를 추가 할 수 있습니다.
James Perih

4

여기에 멋진 Jakob Egger의 접근 방식의 Swift 버전이 있습니다.

import UIKit

private weak var currentFirstResponder: UIResponder?

extension UIResponder {

    static func firstResponder() -> UIResponder? {
        currentFirstResponder = nil
        UIApplication.sharedApplication().sendAction(#selector(self.findFirstResponder(_:)), to: nil, from: nil, forEvent: nil)
        return currentFirstResponder
    }

    func findFirstResponder(sender: AnyObject) {
        currentFirstResponder = self
    }

}

3

이것은 사용자가 ModalViewController에서 Save / Cancel을 클릭 할 때 UITextField가 firstResponder가 무엇인지 찾기 위해했던 것입니다.

    NSArray *subviews = [self.tableView subviews];

for (id cell in subviews ) 
{
    if ([cell isKindOfClass:[UITableViewCell class]]) 
    {
        UITableViewCell *aCell = cell;
        NSArray *cellContentViews = [[aCell contentView] subviews];
        for (id textField in cellContentViews) 
        {
            if ([textField isKindOfClass:[UITextField class]]) 
            {
                UITextField *theTextField = textField;
                if ([theTextField isFirstResponder]) {
                    [theTextField resignFirstResponder];
                }

            }
        }

    }

}

2

이것이 내 UIViewController 범주에있는 것입니다. 첫 번째 응답자를 얻는 것을 포함하여 많은 것들에 유용합니다. 블록은 훌륭합니다!

- (UIView*) enumerateAllSubviewsOf: (UIView*) aView UsingBlock: (BOOL (^)( UIView* aView )) aBlock {

 for ( UIView* aSubView in aView.subviews ) {
  if( aBlock( aSubView )) {
   return aSubView;
  } else if( ! [ aSubView isKindOfClass: [ UIControl class ]] ){
   UIView* result = [ self enumerateAllSubviewsOf: aSubView UsingBlock: aBlock ];

   if( result != nil ) {
    return result;
   }
  }
 }    

 return nil;
}

- (UIView*) enumerateAllSubviewsUsingBlock: (BOOL (^)( UIView* aView )) aBlock {
 return [ self enumerateAllSubviewsOf: self.view UsingBlock: aBlock ];
}

- (UIView*) findFirstResponder {
 return [ self enumerateAllSubviewsUsingBlock:^BOOL(UIView *aView) {
  if( [ aView isFirstResponder ] ) {
   return YES;
  }

  return NO;
 }];
}


2

다음 UIView확장자를 선택하여 얻을 수 있습니다 (Daniel의 신용) :

extension UIView {
    var firstResponder: UIView? {
        guard !isFirstResponder else { return self }
        return subviews.first(where: {$0.firstResponder != nil })
    }
}

1

다음과 같이 시도해보십시오.

- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event { 

    for (id textField in self.view.subviews) {

        if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) {
            [textField resignFirstResponder];
        }
    }
} 

나는 그것을 시도하지 않았지만 좋은 해결책 인 것 같습니다.


1

romeo https://stackoverflow.com/a/2799675/661022 의 솔루션 은 훌륭하지만 코드에 루프가 하나 더 필요하다는 것을 알았습니다. tableViewController로 작업하고있었습니다. 스크립트를 편집 한 후 확인했습니다. 모든 것이 완벽하게 작동했습니다.

나는 이것을 시도하는 것이 좋습니다.

- (void)findFirstResponder
{
    NSArray *subviews = [self.tableView subviews];
    for (id subv in subviews )
    {
        for (id cell in [subv subviews] ) {
            if ([cell isKindOfClass:[UITableViewCell class]])
            {
                UITableViewCell *aCell = cell;
                NSArray *cellContentViews = [[aCell contentView] subviews];
                for (id textField in cellContentViews)
                {
                    if ([textField isKindOfClass:[UITextField class]])
                    {
                        UITextField *theTextField = textField;
                        if ([theTextField isFirstResponder]) {
                            NSLog(@"current textField: %@", theTextField);
                            NSLog(@"current textFields's superview: %@", [theTextField superview]);
                        }
                    }
                }
            }
        }
    }
}

감사! 맞습니다. 여기에 제시된 대부분의 답변은 uitableview로 작업 할 때 해결책이 아닙니다. 코드를 많이 단순화하고 if를 제거하여 if ([textField isKindOfClass:[UITextField class]])uitextview를 사용할 수 있으며 첫 번째 응답자를 반환 할 수 있습니다.
Frade

1

이것은 재귀에 좋은 후보입니다! UIView에 카테고리를 추가 할 필요가 없습니다.

사용법 (뷰 컨트롤러에서) :

UIView *firstResponder = [self findFirstResponder:[self view]];

암호:

// This is a recursive function
- (UIView *)findFirstResponder:(UIView *)view {

    if ([view isFirstResponder]) return view; // Base case

    for (UIView *subView in [view subviews]) {
        if ([self findFirstResponder:subView]) return subView; // Recursion
    }
    return nil;
}

1

이처럼 개인 API를 호출 할 수 있습니다. 애플은 무시합니다.

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];

1
하지만 ... 'respondsToSelector'를 사용하여 확인하십시오
ibcker

지금 'respondsToSelector'를 확인했지만 여전히 경고가 표시됩니다. 안전하지 않을 수 있습니다. 어떻게 든 경고를 제거 할 수 있습니까?
ecth

1

@ thomas-müller 님 의 답변에 대한 스위프트 버전

extension UIView {

    func firstResponder() -> UIView? {
        if self.isFirstResponder() {
            return self
        }

        for subview in self.subviews {
            if let firstResponder = subview.firstResponder() {
                return firstResponder
            }
        }

        return nil
    }

}

1

나는 여기에서 가장 다른 접근 방식이 있습니다. isFirstResponder설정된 뷰를 찾는 뷰 컬렉션을 반복하지 않고 에 메시지를 보내지 nil만 수신자 (있는 경우)를 저장 한 다음 해당 값을 반환하여 호출자가 실제 인스턴스를 가져옵니다 (있는 경우) .

import UIKit

private var _foundFirstResponder: UIResponder? = nil

extension UIResponder{

    static var first:UIResponder?{

        // Sending an action to 'nil' implicitly sends it to the
        // first responder, where we simply capture it for return
        UIApplication.shared.sendAction(#selector(UIResponder.storeFirstResponder(_:)), to: nil, from: nil, for: nil)

        // The following 'defer' statement executes after the return
        // While I could use a weak ref and eliminate this, I prefer a
        // hard ref which I explicitly clear as it's better for race conditions
        defer {
            _foundFirstResponder = nil
        }

        return _foundFirstResponder
    }

    @objc func storeFirstResponder(_ sender: AnyObject) {
        _foundFirstResponder = self
    }
}

그런 다음이 작업을 수행하여 첫 번째 응답자를 사임 할 수 있습니다 ...

return UIResponder.first?.resignFirstResponder()

하지만 지금도 할 수 있습니다 ...

if let firstResponderTextField = UIResponder?.first as? UITextField {
    // bla
}

1

UIView 어디에서나 첫 번째 응답자를 찾기 위해 구현과 공유하고 싶습니다. 나는 그것이 영어에 도움이되고 미안하기를 바랍니다. 감사

+ (UIView *) findFirstResponder:(UIView *) _view {

    UIView *retorno;

    for (id subView in _view.subviews) {

        if ([subView isFirstResponder])
        return subView;

        if ([subView isKindOfClass:[UIView class]]) {
            UIView *v = subView;

            if ([v.subviews count] > 0) {
                retorno = [self findFirstResponder:v];
                if ([retorno isFirstResponder]) {
                    return retorno;
                }
            }
        }
    }

    return retorno;
}

0

아래 코드가 작동합니다.

- (id)ht_findFirstResponder
{
    //ignore hit test fail view
    if (self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES) {
        return nil;
    }
    if ([self isKindOfClass:[UIControl class]] && [(UIControl *)self isEnabled] == NO) {
        return nil;
    }

    //ignore bound out screen
    if (CGRectIntersectsRect(self.frame, [UIApplication sharedApplication].keyWindow.bounds) == NO) {
        return nil;
    }

    if ([self isFirstResponder]) {
        return self;
    }

    for (UIView *subView in self.subviews) {
        id result = [subView ht_findFirstResponder];
        if (result) {
            return result;
        }
    }
    return nil;
}   

0

업데이트 : 내가 틀렸다. UIApplication.shared.sendAction(_:to:from:for:)이 링크에 표시된 첫 번째 응답자 ( http://stackoverflow.com/a/14135456/746890) 를 호출하는 데 실제로 사용할 수 있습니다 .


여기의 대부분의 답변은 현재 첫 번째 응답자가보기 계층 구조에 있지 않으면 실제로 찾을 수 없습니다. 예를 들어, AppDelegate또는 UIViewController서브 클래스.

첫 번째 응답자 객체가 아닌 경우에도 찾을 수있는 방법이 있습니다 UIView.

먼저 다음 next속성을 사용하여 역 버전을 구현할 수 있습니다 UIResponder.

extension UIResponder {
    var nextFirstResponder: UIResponder? {
        return isFirstResponder ? self : next?.nextFirstResponder
    }
}

이 계산 된 속성을 사용하면 현재 첫 번째 응답자가 아닌 경우에도 맨 아래에서 맨 위까지 찾을 수 있습니다 UIView. 예를 들어 a view에서UIViewController 뷰 컨트롤러가 첫 번째로 반응하는 경우, 그것을 관리 누가.

그러나 var현재 첫 번째 응답자를 얻으려면 하향식 해결책이 필요합니다 .

먼저 뷰 계층 구조를 사용하십시오.

extension UIView {
    var previousFirstResponder: UIResponder? {
        return nextFirstResponder ?? subviews.compactMap { $0.previousFirstResponder }.first
    }
}

첫 번째 응답자를 거꾸로 검색하고 찾을 수 없으면 하위 뷰가 동일한 작업을 수행하도록 하위 뷰에 지시합니다 (하위 뷰의 next 자체가 반드시 필요한 ). 이것으로 우리는를 포함한 모든 뷰에서 찾을 수 있습니다 UIWindow.

마지막으로 다음과 같이 빌드 할 수 있습니다.

extension UIResponder {
    static var first: UIResponder? {
        return UIApplication.shared.windows.compactMap({ $0.previousFirstResponder }).first
    }
}

따라서 첫 번째 응답자를 검색하려면 다음을 호출하십시오.

let firstResponder = UIResponder.first

0

첫 번째 응답자를 찾는 가장 간단한 방법 :

func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool

기본 구현은 조치 메소드를 지정된 대상 오브젝트 또는 대상이 지정되지 않은 경우 첫 번째 응답자에게 전달합니다.

다음 단계:

extension UIResponder
{
    private weak static var first: UIResponder? = nil

    @objc
    private func firstResponderWhereYouAre(sender: AnyObject)
    {
        UIResponder.first = self
    }

    static var actualFirst: UIResponder?
    {
        UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
        return UIResponder.first
    }
}

사용법 : UIResponder.actualFirst당신의 목적을 위해 얻으 십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.