개인 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
'Programming' 카테고리의 다른 글
객체 배열에서 속성의 최대 값 찾기 (0) | 2020.03.05 |
---|---|
EditText 외부를 클릭 한 후 안드로이드에서 소프트 키보드를 숨기는 방법은 무엇입니까? (0) | 2020.03.05 |
프로덕션 용 Angular 앱 번들 방법 (0) | 2020.03.05 |
자식 커밋 및 태그를 동시에 푸시 (0) | 2020.03.05 |
kotlin 소스 파일을 Java 소스 파일로 변환하는 방법 (0) | 2020.03.04 |