문제

일주일 전에 앱을 제출하고 오늘 무서운 거부 이메일을 받았습니다. 비공개 API를 사용하고 있기 때문에 내 앱을 받아 들일 수 없다는 것을 알려줍니다. 구체적으로, 그것은 말합니다.

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

이제 불쾌한 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]

첫 번째 응답자를 조작하는 일반적인 방법은 NIL 타겟팅 조치를 사용하는 것입니다. 이것은 응답자 체인 (첫 번째 응답자부터 시작)에 임의의 메시지를 보내고 누군가가 메시지에 응답 할 때까지 (선택기와 일치하는 메소드를 구현 했음) 체인을 계속 내리는 방법입니다.

키보드를 무시하는 경우, 이것은 어떤 창이나보기에 관계없이 가장 효과적인 방법입니다.

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

그것은 예쁘지는 않지만 응답자가 무엇인지 모르면 FIRSTRONGER를 사임하는 방식은 다음과 같습니다.

IB 또는 프로그래밍 방식으로 uitextfield를 만듭니다. 숨겨지게하십시오. IB로 만든 경우 코드에 연결하십시오.

그런 다음 키보드를 기각하려면 응답자를 보이지 않는 텍스트 필드로 전환하고 즉시 사임합니다.

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

Swift 3 & 4 버전의 경우 네빈 대답:

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

다음은 올바른 첫 번째 응답자를보고하는 솔루션입니다 (많은 다른 솔루션은 UIViewController 예를 들어 첫 번째 응답자로서)는 View 계층을 통한 루핑을 필요로하지 않으며 개인 API를 사용하지 않습니다.

애플의 방법을 활용합니다 보내기 : 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의 모든 인스턴스 일 수 있으므로 UIViews에도 불구하고 첫 번째 응답자 일 수있는 다른 클래스가 있습니다. 예를 들어 UIViewController 첫 번째 응답자 일 수도 있습니다.

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

그런 다음 첫 번째 응답자를 검색 할 수 있습니다

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

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

그러나 첫 번째 응답자가 UIView 또는 UIViewController의 서브 클래스가 아닌 경우이 접근법이 실패합니다.

이 문제를 해결하기 위해 카테고리를 만들어 다른 접근 방식을 수행 할 수 있습니다. UIResponder 그리고이 수업의 모든 살아있는 인스턴스를 만들 수 있도록 마술 스위이즈를 수행하십시오. 그런 다음 첫 번째 응답자를 얻으려면 간단한 반복을하고 각 물체를 물어볼 수 있습니다. -isFirstResponder.

이 접근법은 구현 될 수 있습니다 이 다른 요점.

도움이되기를 바랍니다.

사용 빠른 그리고 a 특정한 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)

결과는 an입니다 선택적 값 따라서 주어진 뷰 서브 뷰에서 FIRSTRONSER가 발견되지 않은 경우에는 NIL이됩니다.

첫 번째 응답자가 될 수있는 견해를 반복하고 사용하십시오. - (BOOL)isFirstResponder 그들이 현재 있는지 확인하기 위해.

Peter Steinberger 그냥 트윗 개인 알림에 대해 UIWindowFirstResponderDidChangeNotification, FIRSTRESTRENDER 변경을보고 싶은지 관찰 할 수 있습니다.

사용자가 배경 영역에 탭할 때 키보드를 죽여야하는 경우 제스처 인식기를 추가하고 사용하여 [[self view] endEditing:YES] 메시지?

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

그런 다음 이런 것 같습니다

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

여기에 굉장한 Jakob Egger의 접근 방식의 신속한 버전입니다.

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에서 사용자가 저장/취소를 클릭 할 때 FIRSTROPNERD의 UITEXTFIELD를 찾기 위해 한 일입니다.

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

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

로미오의 해결책 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에 카테고리를 추가 할 필요가 없습니다.

사용법 (View Controller) :

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

Apple은 다음과 같이 Privite API를 호출 할 수 있습니다. Apple은 다음을 무시합니다.

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

UIView의 어느 곳에서나 Find First Responder를위한 제 구현을 여러분과 공유하고 싶습니다. 영어에 도움이되고 죄송합니다. 감사

+ (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.


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

첫 번째 응답자 객체가 UIView.

먼저 반전 된 버전을 구현할 수 있습니다. next 의 자산 UIResponder:

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

이 계산 된 속성을 사용하면 현재 첫 번째 응답자를 바닥에서 상단으로 찾을 수 있습니다. UIView. 예를 들어, a view ~로 UIViewController 뷰 컨트롤러가 첫 번째 응답자 인 경우 누가 관리하고 있습니까?

그러나 우리는 여전히 하향식 해상도, 단일이 필요합니다. var 현재 첫 번째 응답자를 얻으려면.

먼저 View Hierarchy :

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
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top