Uzyskaj aktualną pierwszą odpowiedź bez korzystania z prywatnego interfejsu API
Wysłałem swoją aplikację nieco ponad tydzień temu i dostałem dzisiaj przerażający e-mail z odrzuceniem. Mówi mi, że moja aplikacja nie może zostać zaakceptowana, ponieważ używam Niepublicznego API; w szczególności mówi,
Niepubliczne API, które jest zawarte w aplikacji jest firstResponder.
Teraz, obrażające wywołanie API jest w rzeczywistości rozwiązaniem, które znalazłem tutaj na SO:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
Jak wyświetlić na ekranie aktualną pierwszą odpowiedź? Szukam sposobu, który nie dostanie mojej aplikacji odrzucony.
25 answers
W jednej z moich aplikacji często chcę, aby pierwszy respondent zrezygnował, jeśli użytkownik dotknie w tle. W tym celu napisałem kategorię na UIView, którą nazywam na UIWindow.
Na tej podstawie przedstawiono poniżej i należy zwrócić osobę, która udzieliła pierwszej odpowiedzi.
@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;
}
Swift:
extension UIView {
var firstResponder: UIView? {
guard !isFirstResponder else { return self }
for subview in subviews {
if let firstResponder = subview.firstResponder {
return firstResponder
}
}
return nil
}
}
Przykład użycia w języku Swift:
if let firstResponder = view.window?.firstResponder {
// do something with `firstResponder`
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-23 15:56:33
Jeśli twoim ostatecznym celem jest rezygnacja z pierwszej odpowiedzi, powinno to zadziałać: [self.view endEditing:YES]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-11 12:25:56
Powszechnym sposobem manipulowania ratownikiem jest użycie zero ukierunkowanych działań. Jest to sposób wysyłania dowolnej wiadomości do łańcucha odpowiedzi (zaczynając od pierwszego odpowiedzi) i kontynuowania łańcucha aż ktoś odpowie na wiadomość(zaimplementował metodę pasującą do selektora).
W przypadku odrzucenia klawiatury jest to najskuteczniejszy sposób, który będzie działał bez względu na to, które okno lub widok jest pierwszą odpowiedzią:
[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
To powinno być bardziej skuteczne niż nawet [self.view.window endEditing:YES]
.
(podziękowania dla BigZaphod za przypomnienie mi koncepcji)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-01 22:05:57
Oto kategoria, która pozwala szybko znaleźć pierwszego ratownika dzwoniąc [UIResponder currentFirstResponder]
. Wystarczy dodać dwa następujące pliki do projektu:
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
Sztuczka polega na tym, że wysłanie akcji do nil wysyła ją do pierwszego ratownika.
(pierwotnie opublikowałem tę odpowiedź tutaj: https://stackoverflow.com/a/14135456/322427 )
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:31
Oto rozszerzenie zaimplementowane w Swift oparte na najdoskonalszej odpowiedzi Jakoba Eggera:
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
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-07 19:15:27
Nie jest ładny, ale sposób w jaki rezygnuję z pierwszej odpowiedzi, Gdy Nie wiem, co to jest Odpowiedź:
Utwórz pole UITextField w IB lub programowo. Ukryj to. Podlinkuj go do swojego kodu, jeśli zrobiłeś to w IB.
Następnie, gdy chcesz zwolnić klawiaturę, przełączasz responder na niewidoczne pole tekstowe i natychmiast rezygnujesz z niego:
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-24 18:33:29
Dla wersji Swift 3 i 4nevyn ' S ODPOWIEDŹ:
UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 02:45:28
Oto rozwiązanie, które zgłasza poprawnego ratownika (wiele innych rozwiązań nie zgłasza na przykład UIViewController
jako ratownika), nie wymaga zapętlania hierarchii widoku i nie używa prywatnych interfejsów API.
Wykorzystuje metodę Apple sendAction:to:from:forEvent: , która już wie, jak uzyskać dostęp do osoby udzielającej pierwszej pomocy.
Po prostu musimy to poprawić na 2 sposoby:
- Extend
UIResponder
tak aby mógł wykonać nasz własny kod na pierwszym / align = "left" / - podklasa
UIEvent
w celu zwrócenia pierwszej odpowiedzi.
Oto kod:
@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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-07 01:54:55
Pierwszą odpowiedzią może być dowolna instancja klasy UIResponder, więc istnieją inne klasy, które mogą być pierwszą odpowiedzią pomimo UIViews. Na przykład UIViewController
może być również pierwszą odpowiedzią.
W ten gist znajdziesz rekurencyjny sposób uzyskania pierwszego odpowiedzi poprzez zapętlenie hierarchii kontrolerów zaczynając od kontrolera rootViewController w oknach aplikacji.
Możesz pobrać wtedy pierwszą odpowiedź przez doing
- (void)foo
{
// Get the first responder
id firstResponder = [UIResponder firstResponder];
// Do whatever you want
[firstResponder resignFirstResponder];
}
Jednakże, jeśli first responder nie jest podklasą UIView lub UIViewController, takie podejście zakończy się niepowodzeniem.
Aby rozwiązać ten problem, możemy zrobić inne podejście, tworząc kategorię na UIResponder
i wykonać magiczne świstanie, aby móc zbudować tablicę wszystkich żywych instancji tej klasy. Następnie, aby uzyskać pierwszą odpowiedź, możemy łatwo iterację i zapytać każdy obiekt, czy -isFirstResponder
.
To podejście można znaleźć zaimplementowane w tej drugiej gist .
Mam nadzieję, że to pomoże.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-23 16:54:28
Używając Swift i z specyficznym UIView
obiektem może to pomóc:
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
}
Po prostu umieść go w swoim UIViewController
i użyj go w ten sposób:
let firstResponder = self.findFirstResponder(inView: self.view)
Zwróć uwagę, że wynik jest opcjonalną wartością , więc będzie to nil, jeśli w podwiach podanymi widokami nie znaleziono firstResponser.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-23 21:30:29
Przeanalizuj widoki, które mogą być pierwszą odpowiedzią i użyj - (BOOL)isFirstResponder
, aby określić, czy aktualnie są.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-01 00:37:04
Peter Steinberger właśnie napisał na Twitterze o prywatnym powiadomieniu UIWindowFirstResponderDidChangeNotification
, które możesz obserwować, jeśli chcesz oglądać zmianę pierwszyresponder.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-07 14:55:33
Jeśli musisz tylko zabić klawiaturę, gdy użytkownik dotknie obszaru tła, dlaczego nie dodać rozpoznawania gestów i użyć go do wysłania wiadomości [[self view] endEditing:YES]
?
Możesz dodać rozpoznawacz gestów stukania w pliku xib lub storyboard i podłączyć go do akcji,
Wygląda coś takiego, a potem skończone
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer{
[[self view] endEditing:YES];
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-27 11:48:34
To właśnie zrobiłem, aby znaleźć to, co UITextField jest pierwszym responderem, gdy użytkownik kliknie Zapisz/Anuluj w 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];
}
}
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-10 00:08:55
Just it case here is Swift version of awesome Jakob Egger ' s approach:
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
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-27 21:13:03
Oto co mam w kategorii UIViewController. Przydatne do wielu rzeczy, w tym uzyskania pierwszej pomocy. Klocki są świetne!
- (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;
}];
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-24 05:32:09
Z kategorią na UIResponder
, można legalnie poprosić obiekt UIApplication
, Aby powiedział, Kto jest pierwszą osobą odpowiedzialną.
Zobacz to:
Czy jest jakiś sposób, aby zapytać widok iOS, które z jego dzieci ma status pierwszej odpowiedzi?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:46
Możesz spróbować również tak:
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event {
for (id textField in self.view.subviews) {
if ([textField isKindOfClass:[UITextField class]] && [textField isFirstResponder]) {
[textField resignFirstResponder];
}
}
}
Nie próbowałem, ale wydaje się dobre rozwiązanie
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-01 09:44:53
Rozwiązanie z romeo https://stackoverflow.com/a/2799675/661022 jest spoko, ale zauważyłem, że kod potrzebuje jeszcze jednej pętli. Pracowałem z tableViewController. Edytowałem scenariusz, a potem sprawdziłem. Wszystko działało idealnie.
Polecam spróbować:- (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]);
}
}
}
}
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:31
To dobry kandydat do rekurencji! Nie trzeba dodawać kategorii do UIView.
Użycie (z kontrolera widoku):
UIView *firstResponder = [self findFirstResponder:[self view]];
Kod:
// 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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-23 08:31:27
Możesz wywołać privite api w ten sposób, Apple ignoruj:
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; SEL sel = NSSelectorFromString(@"firstResponder"); UIView *firstResponder = [keyWindow performSelector:sel];
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-11 10:11:27
Chciałbym podzielić się z wami moją implementacją dla find first responder w dowolnym miejscu UIView. Mam nadzieję, że to pomoże i przepraszam za mój angielski. Dzięki
+ (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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-22 12:21:56
Swift version of @Thomas-müller 's response
extension UIView {
func firstResponder() -> UIView? {
if self.isFirstResponder() {
return self
}
for subview in self.subviews {
if let firstResponder = subview.firstResponder() {
return firstResponder
}
}
return nil
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:30
Kod poniżej działa.
- (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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-15 13:27:41
Aktualizacja: myliłem się. Możesz rzeczywiście użyć UIApplication.shared.sendAction(_:to:from:for:)
, aby zadzwonić do pierwszego ratownika pokazanego w tym linku: http://stackoverflow.com/a/14135456/746890 .
Większość odpowiedzi tutaj nie może tak naprawdę znaleźć bieżącego ratownika, jeśli nie znajduje się w hierarchii widoku. Na przykład AppDelegate
lub UIViewController
podklasy.
Istnieje sposób, aby zagwarantować, że go znajdziesz, nawet jeśli obiekt pierwszej odpowiedzi nie jest UIView
.
Najpierw zaimplementujmy jego odwróconą wersję, korzystanie z własności next
UIResponder
:
extension UIResponder {
var nextFirstResponder: UIResponder? {
return isFirstResponder ? self : next?.nextFirstResponder
}
}
Z tą obliczoną właściwością, możemy znaleźć aktualną pierwszą odpowiedź od dołu do góry, nawet jeśli nie jest to UIView
. Na przykład, od view
do UIViewController
, kto nim zarządza, jeśli kontroler widoku jest pierwszą osobą odpowiedzialną.
var
, aby uzyskać aktualną pierwszą odpowiedź.
Pierwszy z hierarchią widoku:
extension UIView {
var previousFirstResponder: UIResponder? {
return nextFirstResponder ?? subviews.compactMap { $0.previousFirstResponder }.first
}
}
To będzie szukać pierwszej odpowiedzi wstecz, a gdyby nie mógł go znaleźć, powiedziałby jego podviewom, aby zrobiły to samo (ponieważ jego podview next
niekoniecznie musi być sobą). Dzięki temu możemy go znaleźć z dowolnego widoku, w tym UIWindow
.
I wreszcie możemy zbudować to:
extension UIResponder {
static var first: UIResponder? {
return UIApplication.shared.windows.compactMap({ $0.previousFirstResponder }).first
}
}
Więc kiedy chcesz odzyskać pierwszego ratownika, możesz zadzwonić:
let firstResponder = UIResponder.first
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-22 16:51:30