รับการตอบกลับครั้งแรกในปัจจุบันโดยไม่ต้องใช้ API ส่วนตัว


340

ฉันส่งแอปของฉันเล็กน้อยเมื่อสัปดาห์ที่แล้วและได้รับอีเมลปฏิเสธที่หวั่นไหวในวันนี้ มันบอกฉันว่าแอปของฉันไม่สามารถยอมรับได้เพราะฉันใช้ API ที่ไม่ใช่แบบสาธารณะ โดยเฉพาะมันพูดว่า

API ที่ไม่ใช่สาธารณะที่รวมอยู่ในแอปพลิเคชันของคุณคือ firstResponder

ตอนนี้การเรียก API ที่กระทำผิดนั้นเป็นวิธีแก้ปัญหาที่ฉันพบที่นี่ใน SO:

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

ฉันจะรับการตอบกลับครั้งแรกบนหน้าจอได้อย่างไร ฉันกำลังมองหาวิธีที่จะไม่ปฏิเสธแอปของฉัน


5
คุณสามารถใช้เวทย์มนตร์นี้ได้อย่างยอดเยี่ยม : stackoverflow.com/a/14135456/746890
Chris Nolet

คำตอบ:


338

ในแอปพลิเคชันของฉันฉันมักต้องการให้ผู้ตอบกลับคนแรกลาออกหากผู้ใช้แตะที่พื้นหลัง เพื่อจุดประสงค์นี้ฉันเขียนหมวดหมู่บน 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`
}

278
นี่คือเหตุผลที่เป็นทางออกที่ดีกว่าเพียงแค่การเรียกร้อง[self.view endEditing:YES]?
ทิมซัลลิแวน

69
@ เวลาฉันไม่สามารถทำอย่างนั้นได้ เห็นได้ชัดว่ามันเป็นวิธีที่ง่ายกว่ามากในการลาออกจากการตอบกลับแรก แม้ว่ามันจะไม่ได้ช่วยคำถามต้นฉบับ แต่ก็เพื่อระบุการตอบกลับครั้งแรก
Thomas Müller

17
นอกจากนี้นี่เป็นคำตอบที่ดีกว่าเพราะคุณอาจต้องการทำสิ่งอื่นกับการตอบกลับครั้งแรกมากกว่าลาออก ...
Erik B

3
ควรสังเกตว่าสิ่งนี้พบเฉพาะ UIViews ไม่ใช่ UIResponders อื่น ๆ ที่อาจเป็นผู้ตอบกลับคนแรก (เช่น UIViewController หรือ UIApplication)
Patrick Pijnappel

10
ทำไมความแตกต่างสำหรับ iOS 7 คุณจะไม่ต้องการหักเงินในกรณีนี้หรือไม่
Peter DeWeese

531

หากเป้าหมายสูงสุดของคุณคือการลาออกจากการตอบกลับแรกสิ่งนี้น่าจะได้ผล: [self.view endEditing:YES]


122
สำหรับคุณทุกคนที่บอกว่านี่คือคำตอบของ "คำถาม" คุณสามารถดูคำถามจริงได้หรือไม่? คำถามถามว่าจะได้รับการตอบกลับครั้งแรกในปัจจุบันอย่างไร ไม่ใช่วิธีลาออกจากการตอบกลับแรก
Justin Kredible

2
และเราควรจะเรียกสิ่งนี้ว่า self.view endEditing ที่ไหน
user4951

8
จริงเพียงพอว่านี่ไม่ใช่การตอบคำถาม แต่ก็เป็นคำตอบที่มีประโยชน์มาก ขอบคุณ!
NovaJoe

6
+1 แม้ว่ามันจะไม่ใช่คำตอบสำหรับคำถาม ทำไม? เพราะหลายคนมาที่นี่พร้อมกับคำถามในใจว่าคำตอบนี้ตอบ :) อย่างน้อย 267 (ในขณะนี้) ของพวกเขา ...
Rok Jarc

1
นี่ไม่ใช่การตอบคำถาม
Sasho

186

วิธีทั่วไปในการจัดการกับผู้ตอบคำถามแรกคือการใช้การกระทำที่ไม่มีเป้าหมาย นี่คือวิธีการส่งข้อความโดยพลการไปยังเครือข่ายผู้ตอบกลับ (เริ่มต้นด้วยการตอบกลับแรก) และดำเนินการต่อไปในเครือจนกว่าจะมีคนตอบกลับข้อความ (ได้ใช้วิธีการที่ตรงกับตัวเลือก)

สำหรับกรณีที่ยกเลิกคีย์บอร์ดนี่เป็นวิธีที่มีประสิทธิภาพที่สุดที่จะทำงานไม่ว่าหน้าต่างหรือมุมมองใดจะตอบกลับมาก่อน:

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

ควรมีประสิทธิภาพมากกว่า[self.view.window endEditing:YES]นี้

(ขอบคุณBigZaphod ที่ทำให้ฉันนึกถึงแนวคิด)


4
มันเจ๋งมาก อาจเป็นเคล็ดลับ Cocoa Touch ที่ประณีตที่สุดเท่าที่ฉันเคยเห็นมา ช่วยฉันไม่สิ้นสุด!
mluisbrown

นี่คือทางออกที่ดีที่สุดโดยไกลขอบคุณหนึ่งล้าน! นี่เป็นวิธีที่ดีกว่าวิธีแก้ปัญหาข้างต้นเพราะมันทำงานได้แม้ใน textfield ที่ใช้งานอยู่เป็นส่วนหนึ่งของมุมมองอุปกรณ์เสริมของแป้นพิมพ์ (ในกรณีนี้มันไม่ได้เป็นส่วนหนึ่งของลำดับชั้นการดูของเรา)
Pizzaiola Gorgonzola

2
วิธีนี้เป็นทางออกที่ดีที่สุด ระบบรู้อยู่แล้วว่าใครเป็นผู้ตอบกลับครั้งแรกไม่จำเป็นต้องค้นหา
leftspin

2
ฉันต้องการให้คำตอบนี้มากกว่าหนึ่ง upvote นี่คืออัจฉริยะ fricking
Reid Main

1
devios1: คำตอบของจาคอบนั้นเป็นสิ่งที่คำถามต้องการ แต่มันเป็นการแฮ็คที่อยู่ด้านบนของระบบตอบกลับแทนที่จะใช้ระบบตอบกลับตามวิธีที่มันถูกออกแบบมา นี่เป็นสิ่งที่สะอาดและบรรลุสิ่งที่คนส่วนใหญ่ถามมาและในหนึ่งซับด้วย (แน่นอนคุณสามารถส่งข้อความสไตล์แอ็คชั่นเป้าหมายและไม่ใช่แค่ลาออกจาก FirstResponder)
nevyn

98

[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

เคล็ดลับที่นี่คือการส่งการกระทำไปยังศูนย์ส่งไปยังผู้ตอบกลับคนแรก

(ตอนแรกฉันเผยแพร่คำตอบนี้ที่นี่: https://stackoverflow.com/a/14135456/322427 )


1
+1 นี่คือทางออกที่ดีที่สุดในการแก้ปัญหา (และคำถามที่ถามจริง) ที่ฉันเคยเห็น นำมาใช้ใหม่และมีประสิทธิภาพ! โหวตขึ้นโหวตคำตอบนี้!
devios1

3
ไชโย นี่อาจเป็นการแฮ็กที่สวยและสะอาดที่สุดเท่าที่ฉันเคยเห็น Objc ...
Aviel Gross

ดีมาก. ควรจะทำงานได้ดีจริงๆสำหรับความสามารถในการถามผู้ตอบกลับคนแรกที่ (ถ้าใคร) ในห่วงโซ่จะจัดการกับวิธีการดำเนินการโดยเฉพาะมันจะถูกส่ง คุณสามารถใช้สิ่งนี้เพื่อเปิดใช้งานหรือปิดใช้งานการควบคุมโดยขึ้นอยู่กับว่าสามารถจัดการได้หรือไม่ ฉันจะตอบคำถามของฉันเองและอ้างอิงคำตอบของคุณ
Benjohn


คำเตือน: วิธีนี้ใช้ไม่ได้หากคุณเริ่มเพิ่มหลายหน้าต่าง ฉันโชคไม่ดีที่ไม่สามารถชี้สาเหตุได้มากกว่านั้น
Heath Borders

41

นี่คือส่วนขยายที่นำมาใช้ใน Swift โดยยึดตามคำตอบที่ดีที่สุดของ 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
    }
}

สวิฟต์ 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
แก้ไขคำตอบสำหรับ swift1.2 ซึ่งสนับสนุน var แบบคงที่
nacho4d

สวัสดีเมื่อฉันพิมพ์ผลของสิ่งนี้ในแอปพลิเคชันมุมมองใหม่ล่าสุดใน viewDidAppear () ฉันจะได้รับไม่มี มุมมองไม่ควรเป็นการตอบกลับครั้งแรก
farhadf

@LinusGeffarth มันไม่เหมาะสมกว่าที่จะเรียกวิธีการคงที่currentแทนfirst?
ผู้บัญชาการรหัส

เดาสิแก้ไขมัน ฉันดูเหมือนจะทิ้งส่วนที่สำคัญเมื่อย่อชื่อตัวแปร
LinusGeffarth

29

มันไม่สวย แต่วิธีที่ฉันลาออกการตอบกลับครั้งแรกเมื่อฉันไม่รู้ว่าการตอบกลับคืออะไร:

สร้าง UITextField ทั้งใน IB หรือโดยทางโปรแกรม ทำให้ซ่อน เชื่อมโยงกับโค้ดของคุณถ้าคุณสร้างเป็น IB

จากนั้นเมื่อคุณต้องการปิดแป้นพิมพ์คุณสลับการตอบกลับไปยังช่องข้อความที่มองไม่เห็นและลาออกทันที:

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

61
นี่คือทั้งน่ากลัวและอัจฉริยะในเวลาเดียวกัน ดี
Alex Wayne

4
ฉันพบว่าเป็นอันตรายเล็กน้อย - Apple อาจตัดสินใจว่าสักวันหนึ่งที่ทำให้การควบคุมที่ซ่อนอยู่กลายเป็นการตอบกลับแรกไม่ใช่พฤติกรรมที่ตั้งใจและ iOS "แก้ไข" ไม่ให้ทำเช่นนี้อีกแล้วเคล็ดลับของคุณอาจหยุดทำงาน
Thomas Tempelmann

2
หรือคุณสามารถกำหนด firstResponder ให้กับ UITextField ที่สามารถมองเห็นได้ในขณะนั้นและเรียกว่า สิ่งนี้ทำให้ไม่จำเป็นต้องสร้างมุมมองที่ซ่อนอยู่ เหมือนกันทั้งหมด +1
Swifty McSwifterton

3
ฉันคิดว่ามันน่ากลัวเพียงอย่างเดียว ... มันอาจเปลี่ยนเค้าโครงแป้นพิมพ์ (หรือมุมมองอินพุต) ก่อนที่จะซ่อนมัน
Daniel


10

ต่อไปนี้เป็นโซลูชันที่รายงานการตอบกลับแรกที่ถูกต้อง (ตัวอย่างโซลูชันอื่น ๆ จะไม่รายงานUIViewControllerว่าเป็นการตอบกลับครั้งแรก) ไม่จำเป็นต้องวนซ้ำไปตามลำดับชั้นการดูและไม่ใช้ API ส่วนตัว

มันใช้ประโยชน์จากวิธีของ Apple sendAction: ถึง: จาก: forEvent:ซึ่งรู้วิธีเข้าถึงผู้ตอบกลับคนแรกอยู่แล้ว

เราเพียงแค่ปรับแต่งมันใน 2 วิธี:

  • ขยาย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

7

การตอบกลับครั้งแรกอาจเป็นตัวอย่างของ UIResponder ของชั้นเรียนดังนั้นจึงมีชั้นเรียนอื่นที่อาจเป็นผู้ตอบกลับครั้งแรกแม้จะมี UIViews ก็ตาม ตัวอย่างเช่นUIViewControllerอาจเป็นการตอบกลับแรก

ในส่วนนี้คุณจะพบวิธีการเรียกซ้ำเพื่อรับการตอบกลับครั้งแรกโดยวนลูปผ่านลำดับชั้นของตัวควบคุมที่เริ่มต้นจาก rootViewController ของหน้าต่างแอปพลิเคชัน

คุณสามารถดึงข้อมูลจากการตอบกลับครั้งแรกด้วยการทำ

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

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

อย่างไรก็ตามหากการตอบกลับครั้งแรกไม่ใช่คลาสย่อยของ UIView หรือ UIViewController วิธีนี้จะล้มเหลว

ในการแก้ไขปัญหานี้เราสามารถทำแนวทางที่แตกต่างได้โดยการสร้างหมวดหมู่UIResponderและทำการ Swizzeling เวทมนต์เพื่อสร้างอาร์เรย์ของอินสแตนซ์ที่มีชีวิตทั้งหมดของคลาสนี้ -isFirstResponderจากนั้นจะได้รับการตอบกลับแรกที่เราสามารถย้ำง่ายและขอให้แต่ละวัตถุถ้า

วิธีการนี้สามารถพบได้ดำเนินการในส่วนอื่น ๆนี้

หวังว่ามันจะช่วย


7

การใช้Swiftและกับวัตถุเฉพาะ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)

รับทราบว่าผลลัพธ์เป็นค่าเผื่อเลือกซึ่งจะเป็นศูนย์ในกรณีที่ไม่พบผู้ตอบสนองคนแรกในการดูย่อยการดู


5

ทำซ้ำมุมมองที่อาจเป็นการตอบกลับแรกและใช้- (BOOL)isFirstResponderเพื่อตรวจสอบว่าพวกเขากำลัง


4

Peter Steinberger เพิ่งเริ่มทวีตเกี่ยวกับการแจ้งเตือนส่วนตัวUIWindowFirstResponderDidChangeNotificationซึ่งคุณสามารถสังเกตได้ว่าคุณต้องการดูการเปลี่ยนแปลงคำตอบแรกหรือไม่


เขาได้รับการปฏิเสธเพราะใช้ API ส่วนตัวอาจจะใช้การแจ้งเตือนส่วนตัวก็อาจจะเป็นความคิดที่ไม่ดี ...
Laszlo

4

หากคุณเพียงแค่ต้องฆ่าแป้นพิมพ์เมื่อผู้ใช้แตะที่พื้นหลังทำไมไม่เพิ่มตัวจดจำท่าทางและใช้มันเพื่อส่ง[[self view] endEditing:YES]ข้อความ?

คุณสามารถเพิ่มตัวจดจำท่าทางการแตะในไฟล์ xib หรือกระดานเรื่องราวและเชื่อมต่อกับการกระทำ

หน้าตาแบบนี้เสร็จแล้ว

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

มันใช้งานได้ดีสำหรับฉัน ฉันเลือกหนึ่งการเชื่อมต่อกับมุมมองบน Xib และฉันสามารถเพิ่มมุมมองเพิ่มเติมสำหรับสิ่งนี้เพื่อเรียกใช้ในคอลเล็กชันอ้างอิงของการอ้างอิง
James Perih

4

เป็นกรณีที่นี่เป็นวิธีที่ยอดเยี่ยมของ 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
    }

}

3

นี่คือสิ่งที่ฉันทำเพื่อค้นหาสิ่งที่ UITextField คือคำตอบแรกเมื่อผู้ใช้คลิกบันทึก / ยกเลิกใน 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];
                }

            }
        }

    }

}

2

นี่คือสิ่งที่ฉันมีในหมวด 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;
 }];
}


2

คุณสามารถเลือกUIViewส่วนขยายต่อไปนี้เพื่อรับมัน(เครดิตโดย Daniel) :

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

1

คุณสามารถลองเช่นนี้:

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

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

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

ฉันไม่ได้ลอง แต่ดูเหมือนจะเป็นทางออกที่ดี


1

การแก้ปัญหาจาก 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]);
                        }
                    }
                }
            }
        }
    }
}

ขอบคุณ! คุณถูกต้องคำตอบที่นำเสนอมากที่สุดไม่ใช่ทางออกเมื่อทำงานกับ uitableview คุณสามารถทำให้รหัสของคุณง่ายขึ้นมากแม้กระทั่งลบออกถ้าif ([textField isKindOfClass:[UITextField class]])อนุญาตให้ใช้สำหรับ uitextview และสามารถคืนค่าการตอบกลับได้
Frade

1

นี่เป็นตัวเลือกที่ดีสำหรับการเรียกซ้ำ! ไม่จำเป็นต้องเพิ่มหมวดหมู่ใน 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;
}

1

คุณสามารถโทรหา privite api แบบนี้ apple ไม่สนใจ:

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

1
แต่ ... โปรดใช้ '
responsesToSelector

ฉันจะตรวจสอบ 'responssToSelector' ตอนนี้ แต่ฉันยังได้รับคำเตือนนี่อาจไม่ปลอดภัย ฉันสามารถกำจัดคำเตือนได้ไหม?
ecth

1

การตอบสนองของ@ 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

ฉันมีวิธีการที่แตกต่างจากที่นี่เล็กน้อยมาก แทนที่จะวนซ้ำผ่านคอลเลกชันของมุมมองที่มองหามุมมองที่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
}

1

ฉันต้องการแบ่งปันการใช้งานของฉันกับคุณในการค้นหาผู้ตอบกลับคนแรกในทุกที่ของ 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;
}

0

รหัสด้านล่างทำงาน

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

อัปเดต: ฉันผิด แน่นอนคุณสามารถใช้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ด้วยคุณสมบัติการคำนวณนี้เราสามารถหาสิ่งที่ตอบกลับแรกปัจจุบันจากด้านล่างขึ้นไปด้านบนถึงแม้ว่ามันจะไม่ได้ ตัวอย่างเช่นจาก a 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

0

วิธีที่ง่ายที่สุดในการค้นหาผู้ตอบกลับคนแรก:

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

การใช้งานเริ่มต้นยื้อวิธีการกระทำเพื่อวัตถุเป้าหมายที่กำหนดหรือถ้าไม่มีการระบุเป้าหมายเพื่อตอบกลับแรก

ขั้นตอนต่อไป:

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

การใช้งาน: เพิ่งได้รับUIResponder.actualFirstเพื่อวัตถุประสงค์ของคุณเอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.