Dapatkan responden pertama saat ini tanpa menggunakan API pribadi

340

Saya mengirimkan aplikasi saya sedikit lebih dari seminggu yang lalu dan mendapat email penolakan yang ditakuti hari ini. Ini memberi tahu saya bahwa aplikasi saya tidak dapat diterima karena saya menggunakan API non-publik; khususnya, katanya,

API non-publik yang termasuk dalam aplikasi Anda adalah firstResponder.

Sekarang, panggilan API yang menyinggung sebenarnya adalah solusi yang saya temukan di sini di SO:

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

Bagaimana cara mendapatkan responden pertama saat ini di layar? Saya mencari cara agar aplikasi saya tidak ditolak.

Justin Kredible
sumber
5
Daripada mengulangi pandangan, Anda dapat menggunakan sedikit sihir yang benar-benar brilian ini : stackoverflow.com/a/14135456/746890
Chris Nolet

Jawaban:

338

Dalam salah satu aplikasi saya, saya sering ingin responden pertama mengundurkan diri jika pengguna mengetuk latar belakang. Untuk tujuan ini saya menulis kategori di UIView, yang saya panggil di UIWindow.

Berikut ini didasarkan pada itu dan harus mengembalikan responden pertama.

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

Cepat:

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

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

        return nil
    }
}

Contoh penggunaan di Swift:

if let firstResponder = view.window?.firstResponder {
    // do something with `firstResponder`
}
Thomas Müller
sumber
278
Mengapa ini solusi yang lebih baik daripada sekadar menelepon [self.view endEditing:YES]?
Tim Sullivan
69
@Tim aku tidak bisa melakukannya. Ini jelas cara yang lebih sederhana untuk mengundurkan diri dari responden pertama. Namun, itu tidak membantu dengan pertanyaan awal, yaitu mengidentifikasi responden pertama.
Thomas Müller
17
Juga, ini adalah jawaban yang lebih baik karena Anda mungkin ingin melakukan sesuatu yang lain dengan responden pertama daripada mengundurkan diri ...
Erik B
3
Perlu dicatat bahwa ini hanya menemukan UIViews, bukan responden UIR lain yang juga bisa menjadi responden pertama (misalnya aplikasi UIViewController atau UIA).
Patrick Pijnappel
10
Mengapa perbedaan untuk iOS 7? tidakkah Anda juga ingin kambuh dalam kasus itu?
Peter DeWeese
531

Jika tujuan akhir Anda hanyalah mengundurkan diri dari responden pertama, ini akan berhasil: [self.view endEditing:YES]

cdyson37
sumber
122
Untuk Anda semua yang mengatakan bahwa ini adalah jawaban untuk "pertanyaan", dapatkah Anda melihat pertanyaan yang sebenarnya? Pertanyaannya adalah bagaimana cara mendapatkan responden pertama saat ini. Bukan cara mengundurkan diri dari responden pertama.
Justin Kredible
2
dan di mana kita harus menyebut ini self.view endEditing?
user4951
8
Cukup benar bahwa ini bukan menjawab pertanyaan, tetapi jelas itu adalah jawaban yang sangat berguna. Terima kasih!
NovaJoe
6
Memberi +1 meskipun itu bukan jawaban untuk pertanyaan. Mengapa? Karena banyak yang datang ke sini dengan pertanyaan bahwa jawaban ini menjawab :) Setidaknya 267 (saat ini) dari mereka ...
Rok Jarc
1
Ini tidak menjawab pertanyaan.
Sasho
186

Cara umum memanipulasi responden pertama adalah dengan menggunakan tindakan nil yang ditargetkan. Ini adalah cara mengirim pesan sewenang-wenang ke rantai responden (dimulai dengan responden pertama), dan melanjutkan ke rantai sampai seseorang menanggapi pesan (telah menerapkan metode yang cocok dengan pemilih).

Untuk kasus penolakan keyboard, ini adalah cara paling efektif yang akan berfungsi tidak peduli jendela atau tampilan mana yang menjadi responden pertama:

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

Ini harus lebih efektif daripada bahkan [self.view.window endEditing:YES].

(Terima kasih kepada BigZaphod untuk mengingatkan saya tentang konsep ini)

nevyn
sumber
4
Ini sangat keren. Mungkin trik Cocoa Touch paling rapi yang pernah saya lihat di SO. Bantu aku tanpa akhir!
mluisbrown
ini JAUH solusi terbaik, terima kasih satu juta! ini jauh lebih baik daripada solusi di atas yang satu ini, karena ini berfungsi bahkan dari bidang teks aktif adalah bagian dari tampilan aksesori keyboard (dalam hal ini bukan bagian dari hierarki tampilan kami)
Pizzaiola Gorgonzola
2
Solusi ini adalah solusi praktik terbaik. Sistem sudah tahu siapa responden pertama, tidak perlu menemukannya.
leftspin
2
Saya ingin memberikan jawaban ini lebih dari satu. Ini jenius yang menakutkan.
Reid Main
1
devios1: Jawaban Jakob persis seperti yang ditanyakan oleh pertanyaan itu, tapi ini adalah retasan di atas sistem responden, daripada menggunakan sistem responden seperti yang dirancang untuk itu. Ini lebih bersih dan menyelesaikan apa yang menjadi alasan kebanyakan orang untuk pertanyaan ini, dan juga dalam satu kalimat. (Tentu saja Anda dapat mengirim pesan gaya aksi target, dan bukan hanya mengundurkan diriResponder).
nevyn
98

Berikut adalah kategori yang memungkinkan Anda untuk dengan cepat menemukan responden pertama dengan menelepon [UIResponder currentFirstResponder]. Cukup tambahkan dua file berikut ke proyek Anda:

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

Kuncinya di sini adalah mengirimkan tindakan ke nol mengirimnya ke responden pertama.

(Saya awalnya menerbitkan jawaban ini di sini: https://stackoverflow.com/a/14135456/322427 )

Jakob Egger
sumber
1
+1 Ini adalah solusi paling elegan untuk masalah (dan pertanyaan aktual yang diajukan) yang pernah saya lihat. Dapat digunakan kembali dan efisien! Hapus jawaban ini!
devios1
3
Bravo. Ini mungkin untuk retas yang paling indah dan bersih yang pernah saya lihat untuk Objc ...
Aviel Gross
Sangat bagus. Harus bekerja dengan sangat baik untuk kemudian dapat bertanya kepada responden pertama siapa (jika ada) dalam rantai akan menangani metode tindakan tertentu jika itu akan dikirim. Anda dapat menggunakan ini untuk mengaktifkan atau menonaktifkan kontrol berdasarkan apakah mereka dapat ditangani. Saya akan menjawab pertanyaan saya sendiri tentang ini dan merujuk jawaban Anda.
Benjohn
Di Swift: stackoverflow.com/questions/1823317/…
Valentin Shergin
Peringatan: Ini tidak berfungsi jika Anda mulai menambahkan banyak jendela. Sayangnya saya belum bisa menunjukkan penyebabnya.
Heath Borders
41

Ini ekstensi yang diterapkan di Swift berdasarkan jawaban Jakob Egger yang paling bagus:

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

Cepat 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
    }
}
Komandan Kode
sumber
1
Jawaban yang dimodifikasi untuk untuk swift1.2 di mana var statis didukung.
nacho4d
Hai, ketika saya mencetak hasil ini pada aplikasi tampilan tunggal baru di viewDidAppear () saya nihil. Tidakkah seharusnya view menjadi responder pertama?
farhadf
@LinusGeffarth Apakah tidak lebih masuk akal untuk memanggil metode statis currentbukan first?
Code Commander
Kira begitu, diedit. Saya sepertinya telah kehilangan bagian penting ketika menyingkat nama variabel.
LinusGeffarth
29

Itu tidak cantik, tetapi cara saya mengundurkan diri responden pertama ketika saya tidak tahu apa itu responden:

Buat UITextField, baik dalam IB atau secara terprogram. Jadikan itu Tersembunyi. Tautkan ke kode Anda jika Anda membuatnya di IB.

Kemudian, ketika Anda ingin mengabaikan keyboard, Anda mengalihkan responden ke bidang teks yang tidak terlihat, dan segera mengundurkan diri:

    [self.invisibleField becomeFirstResponder];
    [self.invisibleField resignFirstResponder];
cannyboy
sumber
61
Ini sekaligus mengerikan sekaligus jenius. Bagus.
Alex Wayne
4
Saya menemukan bahwa agak berbahaya - suatu hari Apple dapat memutuskan bahwa membuat kontrol tersembunyi menjadi responden pertama bukanlah perilaku yang dimaksudkan dan "memperbaiki" iOS untuk tidak melakukan ini lagi, dan kemudian trik Anda mungkin berhenti bekerja.
Thomas Tempelmann
2
Atau Anda dapat menetapkan firstResponder ke UITextField yang saat ini terlihat dan kemudian memanggil resignFirstResponder pada textField tersebut. Ini menghilangkan kebutuhan untuk membuat tampilan tersembunyi. Semua sama, +1.
Swifty McSwifterton
3
Saya pikir ini hanya mengerikan ... mungkin mengubah tata letak keyboard (atau tampilan input) sebelum menyembunyikannya
Daniel
19

Untuk versi Swift 3 & 4 dari nevyn's jawaban :

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

Berikut adalah solusi yang melaporkan responden pertama yang benar (misalnya, banyak solusi lain tidak akan melaporkan UIViewControllersebagai responden pertama), tidak memerlukan pengulangan hierarki tampilan, dan tidak menggunakan API pribadi.

Ini memanfaatkan metode Apple sendAction: to: from: forEvent:, yang sudah tahu cara mengakses responden pertama.

Kita hanya perlu men-tweak dengan 2 cara:

  • Memperpanjang UIResponder sehingga dapat mengeksekusi kode kita sendiri pada responden pertama.
  • Subkelas UIEventuntuk mengembalikan responden pertama.

Ini kodenya:

@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
Masuk akal
sumber
7

Responden pertama dapat berupa instance dari kelas UIResponder, jadi ada kelas lain yang mungkin menjadi responden pertama meskipun UIViews. Sebagai contohUIViewController mungkin juga menjadi responden pertama.

Dalam inti ini Anda akan menemukan cara rekursif untuk mendapatkan responden pertama dengan mengulangi hierarki pengontrol mulai dari rootViewController dari jendela aplikasi.

Anda dapat mengambil responden pertama dengan melakukan

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

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

Namun, jika responden pertama bukan subkelas dari UIView atau UIViewController, pendekatan ini akan gagal.

Untuk memperbaiki masalah ini, kita dapat melakukan pendekatan yang berbeda dengan membuat kategori UIResponderdan melakukan beberapa sihir swizzeling untuk dapat membangun array dari semua instance hidup dari kelas ini. Kemudian, untuk mendapatkan responden pertama kita dapat mengulangi dan menanyakan setiap objek apakah -isFirstResponder.

Pendekatan ini dapat ditemukan diimplementasikan dalam inti lain ini .

Semoga ini bisa membantu.

vilanovi
sumber
7

Menggunakan Swift dan dengan objek tertentuUIView ini mungkin membantu:

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
}

Cukup letakkan di Anda UIViewControllerdan gunakan seperti ini:

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

Perhatikan bahwa hasilnya adalah nilai Opsional sehingga nihil jika tidak ditemukan firstResponser di subview tampilan yang diberikan.

Astaga
sumber
5

Iterate atas pandangan yang bisa menjadi responden pertama dan gunakan - (BOOL)isFirstResponderuntuk menentukan apakah mereka saat ini.

Johan Kool
sumber
4

Peter Steinberger hanya tweet tentang pemberitahuan pribadi UIWindowFirstResponderDidChangeNotification, yang dapat Anda amati jika Anda ingin menonton perubahan responden pertama.

Perbatasan Heath
sumber
Dia ditolak karena menggunakan api pribadi, mungkin menggunakan pemberitahuan pribadi juga bisa menjadi ide yang buruk ...
Laszlo
4

Jika Anda hanya perlu mematikan keyboard ketika pengguna mengetuk area latar belakang mengapa tidak menambahkan pengenal isyarat dan menggunakannya untuk mengirim [[self view] endEditing:YES]pesan?

Anda dapat menambahkan Tap recogniser gesture di file xib atau storyboard dan menghubungkannya ke suatu tindakan,

terlihat seperti ini kemudian selesai

- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
     [[self view] endEditing:YES];
}
Arkadi
sumber
Ini bekerja baik untuk saya. Saya memilih satu koneksi ke tampilan di Xib, dan saya dapat menambahkan tampilan tambahan untuk meminta ini di Referencing Outlet Collections
James Perih
4

Yang penting di sini adalah versi Swift dari pendekatan Jakob Egger yang mengagumkan:

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
    }

}
Valentin Shergin
sumber
3

Inilah yang saya lakukan untuk menemukan apa yang UITextField adalah responden pertama ketika pengguna mengklik Simpan / Batalkan dalam ModalViewController:

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

            }
        }

    }

}
Romeo
sumber
2

Inilah yang saya miliki di Kategori UIViewController saya. Berguna untuk banyak hal, termasuk mendapatkan responden pertama. Blok sangat bagus!

- (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;
 }];
}
Andrei Tchijov
sumber
2

Anda dapat memilih UIViewekstensi berikut untuk mendapatkannya (kredit oleh Daniel) :

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

Anda dapat mencoba juga seperti ini:

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

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

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

Saya tidak mencobanya tetapi sepertinya solusi yang bagus

Frade
sumber
1

Solusi dari romeo https://stackoverflow.com/a/2799675/661022 keren, tapi saya perhatikan bahwa kodenya perlu satu loop lagi. Saya bekerja dengan tableViewController. Saya mengedit skrip dan kemudian saya memeriksa. Semuanya bekerja dengan sempurna.

Saya merekomendasikan untuk mencoba ini:

- (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]);
                        }
                    }
                }
            }
        }
    }
}
pedrouan
sumber
Terima kasih! Anda benar, sebagian besar jawaban yang disajikan di sini bukan solusi saat bekerja dengan uitableview. Anda dapat menyederhanakan banyak kode Anda, bahkan mengeluarkan if if ([textField isKindOfClass:[UITextField class]]), mengizinkan penggunaan untuk uitextview, dan dapat mengembalikan firstresponder.
Frade
1

Ini adalah kandidat yang bagus untuk rekursi! Tidak perlu menambahkan kategori ke UIView.

Penggunaan (dari pengontrol tampilan Anda):

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

Kode:

// 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;
}
Pétur Ingi Egilsson
sumber
1

Anda dapat memanggil api privite seperti ini, abaikan:

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
SEL sel = NSSelectorFromString(@"firstResponder");
UIView   *firstResponder = [keyWindow performSelector:sel];
ibcker
sumber
1
tapi ... silakan gunakan 'respondsToSelector' untuk memeriksa
ibcker
Saya memeriksa 'respondsToSelector' sekarang tetapi saya masih mendapatkan peringatan, ini mungkin tidak aman. Bisakah saya menghilangkan peringatan itu?
ecth
1

Versi cepat dari tanggapan @ 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
    }

}
Kadi
sumber
1

Saya memiliki pendekatan yang sedikit berbeda dari kebanyakan di sini. Daripada mengulangi kumpulan pandangan yang mencari yang telah isFirstResponderditetapkan, saya juga mengirim pesan nil, tetapi saya menyimpan penerima (jika ada), lalu mengembalikan nilai itu sehingga penelepon mendapatkan contoh aktual (lagi, jika ada) .

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

Saya kemudian dapat mengundurkan diri dari responden pertama, jika ada, dengan melakukan ini ...

return UIResponder.first?.resignFirstResponder()

Tapi sekarang saya juga bisa melakukan ini ...

if let firstResponderTextField = UIResponder?.first as? UITextField {
    // bla
}
Mark A. Donohoe
sumber
1

Saya ingin berbagi dengan Anda implementasi saya untuk menemukan responden pertama di UIView. Saya harap ini membantu dan maaf untuk bahasa inggris saya. Terima kasih

+ (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;
}
Rubens Iotti
sumber
0

Kode di bawah ini berfungsi.

- (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;
}   
John Chen
sumber
0

Pembaruan: Saya salah. Anda memang dapat menggunakan UIApplication.shared.sendAction(_:to:from:for:)untuk memanggil responden pertama yang ditunjukkan dalam tautan ini: http://stackoverflow.com/a/14135456/746890 .


Sebagian besar jawaban di sini tidak dapat benar-benar menemukan responden pertama saat ini jika tidak ada dalam hierarki tampilan. Misalnya, AppDelegateatau UIViewControllersubclass.

Ada cara untuk menjamin Anda menemukannya meskipun objek responden pertama bukan a UIView.

Pertama mari kita terapkan versi yang dibalik darinya, menggunakan nextproperti dari UIResponder:

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

Dengan properti yang dihitung ini, kami dapat menemukan responden pertama saat ini dari bawah ke atas walaupun itu tidak UIView. Misalnya, dari a viewkeUIViewController siapa yang mengelolanya, jika pengontrol tampilan adalah responden pertama.

Namun, kami masih membutuhkan resolusi top-down, satu varuntuk mendapatkan responden pertama saat ini.

Pertama dengan hierarki tampilan:

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

Ini akan mencari responden pertama mundur, dan jika tidak dapat menemukannya, ia akan memberi tahu subview untuk melakukan hal yang sama (karena subviewnya nexttidak harus dengan sendirinya). Dengan ini kita dapat menemukannya dari tampilan apa pun, termasuk UIWindow.

Dan akhirnya, kita dapat membangun ini:

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

Jadi ketika Anda ingin mengambil responden pertama, Anda dapat menghubungi:

let firstResponder = UIResponder.first
yesleon
sumber
0

Cara termudah untuk menemukan responden pertama:

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

Implementasi default mengirimkan metode tindakan ke objek target yang diberikan atau, jika tidak ada target yang ditentukan, ke responden pertama.

Langkah berikutnya:

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

Penggunaan: Dapatkan UIResponder.actualFirstuntuk tujuan Anda sendiri.

Alexey
sumber