Nhận phản hồi đầu tiên hiện tại mà không cần sử dụng API riêng


339

Tôi đã gửi ứng dụng của mình hơn một tuần trước và nhận được email từ chối đáng sợ ngày hôm nay. Nó cho tôi biết rằng ứng dụng của tôi không thể được chấp nhận vì tôi đang sử dụng API không công khai; cụ thể, nó nói,

API không công khai được bao gồm trong ứng dụng của bạn là FirstResponder.

Bây giờ, lệnh gọi API vi phạm thực sự là một giải pháp tôi tìm thấy ở đây trên SO:

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

Làm thế nào để tôi có được phản hồi đầu tiên hiện tại trên màn hình? Tôi đang tìm cách không khiến ứng dụng của tôi bị từ chối.


5
Thay vì lặp đi lặp lại qua các lượt xem, bạn có thể sử dụng chút ma thuật thực sự xuất sắc này : stackoverflow.com/a/14135456/746890
Chris Nolet

Câu trả lời:


337

Trong một trong các ứng dụng của mình, tôi thường muốn người phản hồi đầu tiên từ chức nếu người dùng chạm vào nền. Với mục đích này, tôi đã viết một chuyên mục trên UIView, mà tôi gọi trên UIWindow.

Sau đây là dựa trên điều đó và sẽ trả lại phản hồi đầu tiên.

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

Nhanh:

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

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

        return nil
    }
}

Ví dụ sử dụng trong Swift:

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

278
Tại sao đây là một giải pháp tốt hơn là gọi đơn giản [self.view endEditing:YES]?
Tim Sullivan

69
@Tim Tôi không làm bạn có thể làm điều đó. Đó rõ ràng là một cách đơn giản hơn nhiều để từ chức người trả lời đầu tiên. Tuy nhiên, điều đó không giúp ích gì cho các câu hỏi ban đầu, đó là xác định câu trả lời đầu tiên.
Thomas Müller

17
Ngoài ra, đây là một câu trả lời tốt hơn bởi vì bạn có thể muốn làm điều gì đó khác với người trả lời đầu tiên hơn là từ chức ...
Erik B

3
Cần lưu ý rằng điều này chỉ tìm thấy UIView, không phải các UIR tương ứng khác cũng có thể là phản hồi đầu tiên (ví dụ: UIViewControll hoặc UIApplication).
Patrick Pijnappel

10
Tại sao sự khác biệt cho iOS 7? bạn cũng không muốn tái diễn trong trường hợp đó chứ?
Peter De Weese

531

Nếu mục đích cuối cùng của bạn chỉ là từ chức người trả lời đầu tiên, thì điều này sẽ hoạt động: [self.view endEditing:YES]


122
Đối với tất cả các bạn nói rằng đây là câu trả lời cho "câu hỏi", bạn có thể xem câu hỏi thực tế không? Câu hỏi hỏi làm thế nào để có được phản hồi đầu tiên hiện tại. Không phải làm thế nào để từ chức người trả lời đầu tiên.
Justin Kredible

2
và chúng ta nên gọi đây là self.view endEditing ở đâu?
dùng4951

8
Đúng như vậy, đây không phải là trả lời chính xác câu hỏi, nhưng rõ ràng đây là một câu trả lời rất hữu ích. Cảm ơn!
NovaJoe

6
+1 ngay cả khi đó không phải là câu trả lời cho câu hỏi. Tại sao? Bởi vì nhiều người đến đây với một câu hỏi trong đầu rằng câu trả lời này trả lời :) Ít nhất là 267 (tại thời điểm này) trong số họ ...
Rok Jarc

1
Đây không phải là trả lời câu hỏi.
Sasho

186

Một cách phổ biến để thao túng phản hồi đầu tiên là sử dụng các hành động nhắm mục tiêu không. Đây là cách gửi tin nhắn tùy ý đến chuỗi phản hồi (bắt đầu bằng phản hồi đầu tiên) và tiếp tục chuỗi xuống cho đến khi có ai đó trả lời tin nhắn (đã triển khai phương thức khớp với bộ chọn).

Đối với trường hợp tắt bàn phím, đây là cách hiệu quả nhất sẽ hoạt động cho dù cửa sổ hoặc chế độ xem nào là phản hồi đầu tiên:

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

Điều này sẽ hiệu quả hơn thậm chí [self.view.window endEditing:YES].

(Cảm ơn BigZaphod đã nhắc nhở tôi về khái niệm này)


4
Điều này thật tuyệt Có thể là thủ thuật Cacao Touch gọn gàng nhất tôi từng thấy trên SO. Giúp tôi không có kết thúc!
mluisbrown

Đây là BY FAR giải pháp tốt nhất, cảm ơn một triệu! cách này tốt hơn giải pháp trên giải pháp này, bởi vì nó hoạt động ngay cả trường văn bản đang hoạt động là một phần của chế độ xem phụ kiện của bàn phím (trong trường hợp đó không phải là một phần của hệ thống phân cấp chế độ xem của chúng tôi)
Pizzaiola Gorgonzola

2
Giải pháp này là giải pháp thực hành tốt nhất. Hệ thống đã biết người phản hồi đầu tiên là ai, không cần tìm nó.
leftspin

2
Tôi muốn đưa ra câu trả lời này nhiều hơn một upvote. Đây là thiên tài fricking.
Reid Main

1
devios1: Câu trả lời của Jakob chính xác là những gì câu hỏi yêu cầu, nhưng đó là một hack trên đầu hệ thống trả lời, thay vì sử dụng hệ thống trả lời theo cách nó được thiết kế. Điều này là sạch hơn và hoàn thành những gì hầu hết mọi người đến với câu hỏi này, và trong một lót cũng vậy. (Tất nhiên bạn có thể gửi bất kỳ thông báo kiểu hành động mục tiêu nào, và không chỉ là từ chứcFirstResponder).
nevyn

98

Đây là một danh mục cho phép bạn nhanh chóng tìm thấy phản hồi đầu tiên bằng cách gọi [UIResponder currentFirstResponder]. Chỉ cần thêm hai tệp sau vào dự án của bạn:

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

Thủ thuật ở đây là gửi một hành động đến nil sẽ gửi nó đến người phản hồi đầu tiên.

(Ban đầu tôi đã xuất bản câu trả lời này tại đây: https://stackoverflow.com/a/14135456/322427 )


1
+1 Đây là giải pháp tốt nhất cho vấn đề (và câu hỏi thực tế được hỏi) tôi đã thấy. Tái sử dụng và hiệu quả! Upvote câu trả lời này!
devios1

3
Bravo. Đây có thể là bản hack đẹp và sạch nhất tôi từng thấy cho Objc ...
Aviel Gross

Rất đẹp. Nên hoạt động thực sự tốt để sau đó có thể hỏi người trả lời đầu tiên, người (nếu có ai) trong chuỗi sẽ xử lý một phương thức hành động cụ thể mà nó được gửi. Bạn có thể sử dụng điều này để bật hoặc tắt các điều khiển dựa trên việc chúng có thể được xử lý hay không. Tôi sẽ trả lời câu hỏi của riêng tôi về điều này và tham khảo câu trả lời của bạn.
Stewohn


Cảnh báo: Điều này không hoạt động nếu bạn bắt đầu thêm nhiều cửa sổ. Tôi không may không thể chỉ ra một nguyên nhân ngoài đó.
Heath

41

Đây là một Tiện ích mở rộng được triển khai trong Swift dựa trên câu trả lời xuất sắc nhất của Jakob Egger:

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

Swift 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
Câu trả lời đã sửa đổi cho swift1.2 trong đó var tĩnh được hỗ trợ.
nacho4d

Xin chào, khi tôi in kết quả này trên một ứng dụng xem đơn hoàn toàn mới trong viewDidAppear () tôi sẽ nhận được. Không nên xem là người trả lời đầu tiên?
farhadf

@LinusGeffarth Không có nghĩa gì hơn khi gọi phương thức tĩnh currentthay vì first?
Chỉ huy mã

Đoán như vậy, chỉnh sửa nó. Tôi dường như đã bỏ phần quan trọng khi viết tắt tên biến.
LinusGeffarth

29

Nó không đẹp, nhưng cách tôi từ chức Người phản hồi đầu tiên khi tôi không biết người phản hồi đó là gì:

Tạo một UITextField, bằng IB hoặc lập trình. Làm cho nó ẩn. Liên kết nó với mã của bạn nếu bạn thực hiện nó trong IB.

Sau đó, khi bạn muốn tắt bàn phím, bạn chuyển phản hồi sang trường văn bản vô hình và ngay lập tức từ chức nó:

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

61
Điều này vừa kinh khủng vừa là thiên tài cùng một lúc. Đẹp.
Alex Wayne

4
Tôi thấy điều đó hơi nguy hiểm - một ngày nào đó Apple có thể quyết định biến điều khiển ẩn trở thành phản hồi đầu tiên không phải là hành vi có chủ đích và "sửa" iOS không làm điều này nữa, và sau đó thủ thuật của bạn có thể ngừng hoạt động.
Thomas Tempelmann

2
Hoặc bạn có thể gán FirstResponder cho UITextField hiện đang hiển thị và sau đó gọi resignFirstResponder trên textField cụ thể đó. Điều này loại bỏ sự cần thiết phải tạo ra một chế độ xem ẩn. Tất cả đều giống nhau, +1.
Swifty McSwifterton

3
Tôi nghĩ rằng nó chỉ kinh khủng ... nó có thể thay đổi bố cục bàn phím (hoặc chế độ xem đầu vào) trước khi ẩn nó
Daniel

19

Đối với phiên bản Swift 3 & 4 của câu trả lời của nevyn :

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

10

Đây là một giải pháp báo cáo phản hồi đầu tiên chính xác (nhiều giải pháp khác sẽ không báo cáo UIViewController ví dụ, là phản hồi đầu tiên), không yêu cầu lặp qua phân cấp chế độ xem và không sử dụng API riêng.

Nó tận dụng phương thức sendAction của Apple : to: from: forEvent: , đã biết cách truy cập bộ phản hồi đầu tiên.

Chúng ta chỉ cần điều chỉnh nó theo 2 cách:

  • Mở rộng UIResponderđể nó có thể thực thi mã của chúng ta trên bộ phản hồi đầu tiên.
  • Phân lớp UIEventđể trả về phản hồi đầu tiên.

Đây là mã:

@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

Bộ phản hồi đầu tiên có thể là bất kỳ phiên bản nào của lớp UIResponder, vì vậy có những lớp khác có thể là bộ phản hồi đầu tiên mặc dù UIViews. Ví dụ UIViewControllercũng có thể là người trả lời đầu tiên.

Trong ý chính này, bạn sẽ tìm thấy một cách đệ quy để có được phản hồi đầu tiên bằng cách lặp qua hệ thống phân cấp của các bộ điều khiển bắt đầu từ rootViewContaptor của các cửa sổ của ứng dụng.

Bạn có thể truy xuất sau đó phản hồi đầu tiên bằng cách làm

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

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

Tuy nhiên, nếu phần trả lời đầu tiên không phải là lớp con của UIView hoặc UIViewControll, cách tiếp cận này sẽ thất bại.

Để khắc phục vấn đề này, chúng ta có thể thực hiện một cách tiếp cận khác bằng cách tạo một thể loại trên UIRespondervà thực hiện một số phép thuật ảo thuật để có thể xây dựng một mảng của tất cả các trường hợp sống của lớp này. Sau đó, để có được phản hồi đầu tiên, chúng ta có thể lặp lại đơn giản và hỏi từng đối tượng nếu -isFirstResponder.

Cách tiếp cận này có thể được tìm thấy thực hiện trong ý chính khác này .

Hy vọng nó giúp.


7

Sử dụng Swift và với một đối tượng cụ thể,UIView điều này có thể giúp:

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
}

Chỉ cần đặt nó trong của bạn UIViewControllervà sử dụng nó như thế này:

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

Hãy lưu ý rằng kết quả là một giá trị Tùy chọn, vì vậy nó sẽ không có trong trường hợp không tìm thấy FirstResponser trong các lượt xem lượt xem đã cho.


5

Lặp lại các quan điểm có thể là phản hồi đầu tiên và sử dụng - (BOOL)isFirstResponderđể xác định xem chúng có hiện tại không.


4

Peter Steinberger vừa tweet về thông báo riêng tư UIWindowFirstResponderDidChangeNotification, bạn có thể quan sát nếu bạn muốn xem thay đổi đầu tiên.


Anh ta bị từ chối vì sử dụng api riêng tư, có thể sử dụng thông báo riêng tư cũng có thể là một ý tưởng tồi ...
Laszlo

4

Nếu bạn chỉ cần tắt bàn phím khi người dùng chạm vào vùng nền tại sao không thêm trình nhận dạng cử chỉ và sử dụng nó để gửi [[self view] endEditing:YES] tin nhắn?

bạn có thể thêm trình nhận dạng cử chỉ Tap trong tệp xib hoặc bảng phân cảnh và kết nối nó với một hành động,

trông như thế này rồi

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

Nó hiệu quả tuyệt vời đối với tôi. Tôi đã chọn một kết nối cho một chế độ xem trên Xib và tôi có thể thêm các chế độ xem bổ sung cho mục này để gọi trong Bộ sưu tập Cửa hàng Tham khảo
James Perih

4

Chỉ có trường hợp ở đây là phiên bản Swift của cách tiếp cận Jakob Egger tuyệt vời:

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

Đây là những gì tôi đã làm để tìm ra UITextField là đầu tiên Phản hồi khi người dùng nhấp vào Lưu / Hủy trong ModalViewCont kiểm soát:

    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

Đây là những gì tôi có trong Danh mục UIViewControll của mình. Hữu ích cho nhiều thứ, bao gồm cả việc trả lời đầu tiên. Khối rất tuyệt!

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

Bạn có thể chọn UIViewtiện ích mở rộng sau để có được nó (tín dụng của Daniel) :

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

1

Bạn cũng có thể thử như thế này:

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

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

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

Tôi đã không thử nó nhưng nó có vẻ là một giải pháp tốt


1

Giải pháp từ romeo https://stackoverflow.com/a/2799675/661022 rất tuyệt, nhưng tôi nhận thấy rằng mã cần thêm một vòng lặp. Tôi đã làm việc với tableViewControll. Tôi chỉnh sửa kịch bản và sau đó tôi kiểm tra. Mọi thứ hoạt động hoàn hảo.

Tôi khuyên bạn nên thử điều này:

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

Cảm ơn! Bạn đã đúng, hầu hết các câu trả lời được trình bày ở đây không phải là một giải pháp khi làm việc với uitableview. Bạn có thể đơn giản hóa mã của mình rất nhiều, thậm chí loại bỏ if if ([textField isKindOfClass:[UITextField class]]), cho phép sử dụng cho uitextview và có thể trả về phản hồi.
Frade

1

Đây là ứng cử viên tốt cho đệ quy! Không cần thêm danh mục vào UIView.

Cách sử dụng (từ trình điều khiển xem của bạn):

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

Mã số:

// 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

bạn có thể gọi api riêng tư như thế này, apple bỏ qua:

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

1
nhưng ... vui lòng sử dụng '
responssToSelector

Tôi hiện kiểm tra 'hồi đáp ToSelector' nhưng tôi vẫn nhận được cảnh báo, điều này có thể không an toàn. Tôi có thể thoát khỏi cảnh báo bằng cách nào đó?
ecth

1

Phiên bản Swift của phản hồi của @ 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

Tôi có một cách tiếp cận hơi khác so với hầu hết ở đây. Thay vì lặp qua bộ sưu tập các khung nhìn tìm kiếm một khung nhìn đã isFirstResponderđặt, tôi cũng gửi tin nhắn đến nil, nhưng tôi lưu trữ máy thu (nếu có), sau đó trả về giá trị đó để người gọi lấy ví dụ thực tế (một lần nữa, nếu có) .

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

Sau đó tôi có thể từ chức người trả lời đầu tiên, nếu có, bằng cách này ...

return UIResponder.first?.resignFirstResponder()

Nhưng bây giờ tôi cũng có thể làm điều này ...

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

1

Tôi muốn chia sẻ với bạn cách triển khai của tôi để tìm phản hồi đầu tiên ở bất cứ nơi nào của UIView. Tôi hy vọng nó sẽ giúp và xin lỗi cho tiếng Anh của tôi. Cảm ơn

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

Mã dưới đây làm việc.

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

Cập nhật: Tôi đã sai. Bạn thực sự có thể sử dụng UIApplication.shared.sendAction(_:to:from:for:)để gọi phản hồi đầu tiên được thể hiện trong liên kết này: http://stackoverflow.com/a/14135456/746890 .


Hầu hết các câu trả lời ở đây thực sự không thể tìm thấy phản hồi đầu tiên hiện tại nếu nó không nằm trong hệ thống phân cấp chế độ xem. Ví dụ, AppDelegatehoặc các UIViewControllerlớp con.

Có một cách để đảm bảo bạn tìm thấy nó ngay cả khi đối tượng phản hồi đầu tiên không phải là a UIView.

Trước tiên, hãy thực hiện phiên bản đảo ngược của nó, sử dụng thuộc nexttính của UIResponder:

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

Với thuộc tính được tính toán này, chúng ta có thể tìm thấy phản hồi đầu tiên hiện tại từ dưới lên trên ngay cả khi nó không UIView . Ví dụ: từ a viewđếnUIViewController ai đang quản lý nó, nếu bộ điều khiển xem là trả lời đầu tiên.

Tuy nhiên, chúng ta vẫn cần một độ phân giải từ trên xuống, một var để có được phản hồi đầu tiên hiện tại.

Đầu tiên với hệ thống phân cấp xem:

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

Điều này sẽ tìm kiếm phản hồi đầu tiên ngược và nếu không thể tìm thấy nó, nó sẽ bảo các cuộc phỏng vấn của nó làm điều tương tự (bởi vì cuộc phỏng vấn của nó next nó không nhất thiết phải là chính nó). Với điều này, chúng tôi có thể tìm thấy nó từ bất kỳ xem, bao gồm cả UIWindow.

Và cuối cùng, chúng ta có thể xây dựng điều này:

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

Vì vậy, khi bạn muốn truy xuất phản hồi đầu tiên, bạn có thể gọi:

let firstResponder = UIResponder.first

0

Cách đơn giản nhất để tìm phản hồi đầu tiên:

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

Việc triển khai mặc định gửi phương thức hành động đến đối tượng đích đã cho hoặc, nếu không có mục tiêu nào được chỉ định, cho phản hồi đầu tiên.

Bước tiếp theo:

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

Cách sử dụng: Chỉ nhận UIResponder.actualFirstcho mục đích riêng của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.