Programming

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

procodes 2020. 3. 5. 08:01
반응형

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


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

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

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

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

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


내 응용 프로그램 중 하나에서 사용자가 백그라운드를 두드리면 첫 번째 응답자가 사직하기를 원합니다. 이를 위해 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`
}

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


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

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

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

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

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


다음을 호출하여 첫 ​​번째 응답자를 빠르게 찾을 수있는 카테고리가 있습니다 [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 )


다음은 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
    }
}

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

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

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

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

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

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

다음은 올바른 첫 번째 응답자를보고하는 솔루션입니다 (예 : 다른 솔루션은 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

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

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

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

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

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

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

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

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

도움이 되길 바랍니다.


사용 스위프트 와 함께 특정하는 것은 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이됩니다.


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


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


사용자가 배경 영역을 누를 때 키보드를 죽여야하는 경우 제스처 인식기를 추가하고이를 사용하여 [[self view] endEditing:YES]메시지 를 보내십시오 .

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

이런 식으로 완성되고

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

여기에 멋진 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
    }

}

이것은 사용자가 ModalViewController에서 저장 / 취소를 클릭 할 때 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];
                }

            }
        }

    }

}

이것이 내 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;
 }];
}

에 카테고리 UIResponder가 있으면 UIApplication첫 번째 응답자가 누구인지를 객체에 법적으로 요청할 수 있습니다.

이것 좀 봐:

자녀 중 어느 쪽이 먼저 응답자 상태인지 iOS보기를 요청하는 방법이 있습니까?


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

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

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

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

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

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

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


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

이것은 재귀에 좋은 후보입니다! 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;
}

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

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

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;

}


@ 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
    }

}

나는 여기에서 가장 다른 접근 방식이 있습니다. 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
}

아래 코드가 작동합니다.

- (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;
}   

업데이트 : 내가 틀렸다. 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. 예를 들어,에서 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

참고 URL : https://stackoverflow.com/questions/1823317/get-the-current-first-responder-without-using-a-private-api



반응형