الحصول على الحالية المستجيب الأول دون استخدام API الخاص
-
22-07-2019 - |
سؤال
قدمت بلدي التطبيق قليلا قبل أكثر من أسبوع وحصلت اللعين رفض البريد الإلكتروني اليوم.يقول لي أن بلدي التطبيق لا يمكن قبوله لأن أنا باستخدام 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
دائرة الرقابة الداخلية 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
}
}
مثال للاستخدام في سويفت:
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.ح
#import <Cocoa/Cocoa.h>
@interface UIResponder (FirstResponder)
+(id)currentFirstResponder;
@end
UIResponder+FirstResponder.م
#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)
وهنا لتمديد تنفيذها في سويفت على أساس الإجابة اكثر من ممتاز جاكوب الرقيب في:
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
}
}
وانها ليست جميلة، ولكن الطريقة I الاستقالة من firstResponder عندما كنت لا أعرف ما أن الرد هو:
وإنشاء UITextField، سواء في IB أو برمجيا. جعلها المخفية. ربط ليصل إلى التعليمات البرمجية الخاصة بك إذا قمت بها في IB.
وبعد ذلك، عندما تريد فصل لوحة المفاتيح، يمكنك التبديل المستجيب لحقل النص غير مرئية، والاستقالة على الفور:
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
من أجل الإسراع 3 & 4 إصدار nevyn هو الجواب:
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
هنا هو الحل الذي التقارير الصحيحة المستجيب الأول (العديد من الحلول الأخرى لا تقرير UIViewController
كما المستجيب الأول ، على سبيل المثال), لا يتطلب حلقات على عرض التسلسل الهرمي ، و لا تستخدم واجهات برمجة التطبيقات الخاصة.
فإنه يستفيد من أبل طريقة 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
والمستجيب الأول يمكن أن يكون أي مثيل 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
.
وهذا النهج يمكن العثور عليها في تنفيذ هذا جوهر البعض.
ونأمل أن يساعد.
باستخدام سويفت و مع محددة 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 وجد في وجهات النظر subviews.
وتكرار عبر وجهات النظر التي يمكن أن يكون المستجيب الأول واستخدام - (BOOL)isFirstResponder
لتحديد ما إذا كانت حاليا.
بالتغريد عن UIWindowFirstResponderDidChangeNotification
الإخطار الخاص، والتي يمكن أن نلاحظ إذا كنت تريد مشاهدة التغيير firstResponder.
إذا كنت بحاجة فقط لقتل لوحة المفاتيح عند نقر المستخدم على المنطقة الخلفية لماذا لا تضيف إلى التعرف على لفتة واستخدامه لإرسال رسالة [[self view] endEditing:YES]
؟
ويمكنك إضافة recogniser الحنفية فتة في ملف xib أو القصة المصورة وتوصيله إلى العمل،
ويبدو شيئا من هذا القبيل الانتهاء ثم
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
[[self view] endEditing:YES];
}
وفقط هو الحال هنا هو الإصدار سريع من نهج رهيبة جاكوب الرقيب في:
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
}
}
وهذا هو ما فعلته للعثور على ما هو UITextField وfirstResponder عندما ينقر المستخدم حفظ / الغاء في 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];
}
}
}
}
}
وهذا هو ما قلته في بلدي 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
أن أقول لكم من هو المستجيب الأول.
وانظر هذا:
<وأ href = "https://stackoverflow.com/questions/5029267/is-there-any-way-of-asking-an-ios-view-which-of-its-children-has-first- الرد "> هل هناك أي طريقة لطلب وجهة نظر دائرة الرقابة الداخلية التي من أبنائها لها وضع المستجيب الأول؟
ويمكنك اختيار تمديد UIView
التالية للحصول عليه <م> (الائتمان عن طريق دانيال) م>:
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.
والاستخدام (من وحدة تحكم وجهة نظركم):
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 privite مثل هذا، والتفاح تجاهل:
<اقتباس فقرة>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
}
}
هذا بحث عن المستجيب الأول إلى الوراء, و إذا كان لا يمكن العثور عليه ، فإنه أخبر به subviews أن تفعل الشيء نفسه (لأن subview هو next
ليس بالضرورة نفسها).مع هذا نجد من أي عرض ، بما في ذلك UIWindow
.
و أخيرا يمكننا أن نبني هذا:
extension UIResponder {
static var first: UIResponder? {
return UIApplication.shared.windows.compactMap({ $0.previousFirstResponder }).first
}
}
حتى عندما كنت ترغب في استرداد المستجيب الأول ، يمكنك الاتصال على:
let firstResponder = UIResponder.first