简体   繁体   English

在不使用私有 API 的情况下获取当前的第一响应者

[英]Get the current first responder without using a private API

I submitted my app a little over a week ago and got the dreaded rejection email today.我在一个多星期前提交了我的应用程序,今天收到了可怕的拒绝电子邮件。 It tells me that my app cannot be accepted because I'm using a non-public API;它告诉我我的应用程序无法被接受,因为我使用的是非公共 API; specifically, it says,具体来说,它说,

The non-public API that is included in your application is firstResponder.您的应用程序中包含的非公共 API 是 firstResponder。

Now, the offending API call is actually a solution I found here on SO:现在,有问题的 API 调用实际上是我在 SO 上找到的解决方案:

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

How do I get the current first responder on the screen?如何在屏幕上显示当前的第一响应者? I'm looking for a way that won't get my app rejected.我正在寻找一种不会让我的应用程序被拒绝的方法。

如果你的最终目标只是让第一响应者辞职,这应该有效: [self.view endEditing:YES]

In one of my applications I often want the first responder to resign if the user taps on the background.在我的一个应用程序中,如果用户点击背景,我经常希望第一响应者辞职。 For this purpose I wrote a category on UIView, which I call on the UIWindow.为此,我在 UIView 上写了一个类别,我在 UIWindow 上调用它。

The following is based on that and should return the first responder.以下内容基于此,应返回第一响应者。

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

Usage example in Swift: Swift 中的用法示例:

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

A common way of manipulating the first responder is to use nil targeted actions.操纵第一响应者的常见方法是使用 nil 目标操作。 This is a way of sending an arbitrary message to the responder chain (starting with the first responder), and continuing down the chain until someone responds to the message (has implemented a method matching the selector).这是一种将任意消息发送到响应者链(从第一个响应者开始)并继续沿链向下直到有人响应该消息(已实现与选择器匹配的方法)的方式。

For the case of dismissing the keyboard, this is the most effective way that will work no matter which window or view is first responder:对于关闭键盘的情况,无论哪个窗口或视图是第一响应者,这是最有效的方法:

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

This should be more effective than even [self.view.window endEditing:YES] .这应该甚至比[self.view.window endEditing:YES]更有效。

(Thanks to BigZaphod for reminding me of the concept) (感谢BigZaphod提醒我这个概念)

Here's a category that allows you to quickly find the first responder by calling [UIResponder currentFirstResponder] .这是一个类别,可让您通过调用[UIResponder currentFirstResponder]快速找到第一响应者。 Just add the following two files to your project:只需将以下两个文件添加到您的项目中:

UIResponder+FirstResponder.h UIResponder+FirstResponder.h

#import <Cocoa/Cocoa.h>
@interface UIResponder (FirstResponder)
    +(id)currentFirstResponder;
@end

UIResponder+FirstResponder.m 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

The trick here is that sending an action to nil sends it to the first responder.这里的技巧是将一个动作发送到 nil 将它发送给第一响应者。

(I originally published this answer here: https://stackoverflow.com/a/14135456/322427 ) (我最初在这里发布了这个答案: https : //stackoverflow.com/a/14135456/322427

Here is a Extension implemented in Swift based on Jakob Egger's most excellent answer:这是基于 Jakob Egger 最优秀答案在 Swift 中实现的扩展:

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斯威夫特 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
    }
}

It's not pretty, but the way I resign the firstResponder when I don't know what that the responder is:这不是很漂亮,但是当我不知道响应者是什么时,我退出 firstResponder 的方式:

Create an UITextField, either in IB or programmatically.在 IB 中或以编程方式创建一个 UITextField。 Make it Hidden.使其隐藏。 Link it up to your code if you made it in IB.如果您是在 IB 中制作的,则将其链接到您的代码。

Then, when you want to dismiss the keyboard, you switch the responder to the invisible text field, and immediately resign it:然后,当你想关闭键盘时,你将响应者切换到不可见的文本字段,并立即辞职:

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

对于 Swift 3 & 4 版本的nevyn答案:

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

Here's a solution which reports the correct first responder (many other solutions won't report a UIViewController as the first responder, for example), doesn't require looping over the view hierarchy, and doesn't use private APIs.这是一个报告正确的第一响应者的解决方案(例如,许多其他解决方案不会将UIViewController报告为第一响应者),不需要循环遍历视图层次结构,并且不使用私有 API。

It leverages Apple's method sendAction:to:from:forEvent: , which already knows how to access the first responder.它利用了 Apple 的方法sendAction:to:from:forEvent: ,该方法已经知道如何访问第一响应者。

We just need to tweak it in 2 ways:我们只需要通过两种方式调整它:

  • Extend UIResponder so it can execute our own code on the first responder.扩展UIResponder以便它可以在第一响应者上执行我们自己的代码。
  • Subclass UIEvent in order to return the first responder.子类UIEvent以返回第一响应者。

Here is the code:这是代码:

@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

The first responder can be any instance of the class UIResponder, so there are other classes that might be the first responder despite the UIViews.第一响应者可以是 UIResponder 类的任何实例,因此尽管有 UIViews,还有其他类可能是第一响应者。 For example UIViewController might also be the first responder.例如UIViewController也可能是第一响应者。

In this gist you will find a recursive way to get the first responder by looping through the hierarchy of controllers starting from the rootViewController of the application's windows.这个要点中,您将找到一种递归方式,通过从应用程序窗口的 rootViewController 开始循环遍历控制器的层次结构来获得第一响应者。

You can retrieve then the first responder by doing您可以通过执行以下操作来检索第一响应者

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

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

However, if the first responder is not a subclass of UIView or UIViewController, this approach will fail.但是,如果第一响应者不是 UIView 或 UIViewController 的子类,则此方法将失败。

To fix this problem we can do a different approach by creating a category on UIResponder and perform some magic swizzeling to be able to build an array of all living instances of this class.为了解决这个问题,我们可以通过在UIResponder上创建一个类别来做一个不同的方法,并执行一些神奇的 swizzeling 来构建一个包含这个类的所有实例的数组。 Then, to get the first responder we can simple iterate and ask each object if -isFirstResponder .然后,为了获得第一响应者,我们可以简单地迭代并询问每个对象是否为-isFirstResponder

This approach can be found implemented in this other gist .这种方法可以在这个其他要点中找到

Hope it helps.希望能帮助到你。

Using Swift and with a specific UIView object this might help:使用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
}

Just place it in your UIViewController and use it like this:只需将它放在您的UIViewController并像这样使用它:

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

Take note that the result is an Optional value so it will be nil in case no firstResponder was found in the given views subview hierarchy.请注意,结果是一个Optional 值,因此如果在给定的视图子视图层次结构中找不到 firstResponder,它将为零。

迭代可能是第一响应者的视图并使用- (BOOL)isFirstResponder来确定它们当前是否是。

Peter Steinberger 刚刚发布了关于私有通知UIWindowFirstResponderDidChangeNotification推文,如果你想观看 firstResponder 的变化,你可以观察它。

If you just need to kill the keyboard when the user taps on a background area why not add a gesture recognizer and use it to send the [[self view] endEditing:YES] message?如果您只需要在用户点击背景区域时关闭键盘,为什么不添加手势识别器并使用它来发送[[self view] endEditing:YES]消息?

you can add the Tap gesture recogniser in the xib or storyboard file and connect it to an action,您可以在 xib 或故事板文件中添加 Tap 手势识别器并将其连接到一个动作,

looks something like this then finished看起来像这样然后完成

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

Just it case here is Swift version of awesome Jakob Egger's approach:只是这里的情况是令人敬畏的 Jakob Egger 方法的 Swift 版本:

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
    }

}

This is what I did to find what UITextField is the firstResponder when the user clicks Save/Cancel in a ModalViewController:这就是我在用户单击 ModalViewController 中的保存/取消时找到 UITextField 是 firstResponder 所做的工作:

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

            }
        }

    }

}

I have a slightly different approach than most.我的方法与大多数人略有不同。 Rather than iterate through the collection of views looking for the one that has isFirstResponder set, I too send a message to nil , but I store the receiver of the message so I can return it and do whatever I wish with it.我没有遍历视图集合以查找设置了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 and place it in the _foundFirstResponder variable.
        // As such, the variable will contain the current first responder (if any) immediately after this line executes
        UIApplication.shared.sendAction(#selector(UIResponder.storeFirstResponder(_:)), to: nil, from: nil, for: nil)

        // The following 'defer' statement runs *after* this getter returns,
        // thus releasing any strong reference held by the variable immediately thereafter
        defer {
            _foundFirstResponder = nil
        }

        // Return the found first-responder (if any) back to the caller
        return _foundFirstResponder
    }

    @objc func storeFirstResponder(_ sender: AnyObject) {

        // Capture the recipient of this message (self), which is the first responder
        _foundFirstResponder = self
    }
}

With the above, I can resign the first responder by simply doing this...有了上述内容,我可以通过简单地这样做来辞去第一响应者的职务......

UIResponder.first?.resignFirstResponder()

But since my API actually hands back whatever the first responder is, I can do whatever I want with it.但是由于我的 API 实际上交还了第一响应者是什么,我可以用它做任何我想做的事情。

Here's an example that checks if the current first responder is a UITextField with a helpMessage property set, and if so, shows it in a help bubble right next to the control.这是一个示例,用于检查当前的第一响应者是否是设置了helpMessage属性的UITextField ,如果是,则将其显示在控件旁边的帮助气泡中。 We call this from a 'Quick Help' button on our screen.我们通过屏幕上的“快速帮助”按钮调用它。

func showQuickHelp(){

    if let textField   = UIResponder?.first as? UITextField,
       let helpMessage = textField.helpMessage {
    
        textField.showHelpBubble(with:helpMessage)
    }
}

The support for the above is defined in an extension on UITextField like so...对上述内容的支持是在UITextField的扩展中定义的,就像这样......

extension UITextField {
    var helpMessage:String? { ... }
    func showHelpBubble(with message:String) { ... }
}

Now to support this feature, all we have to do is decide which text fields have help messages and the UI takes care of the rest for us.现在要支持此功能,我们所要做的就是决定哪些文本字段具有帮助消息,而 UI 会为我们处理其余的工作。

With a category on UIResponder , it is possible to legally ask the UIApplication object to tell you who the first responder is.使用UIResponder上的类别,可以合法地要求UIApplication对象告诉您谁是第一响应者。

See this:看到这个:

Is there any way of asking an iOS view which of its children has first responder status? 有什么方法可以询问 iOS 视图的哪个孩子具有第一响应者状态?

You can choose the following UIView extension to get it (credit by Daniel) :您可以选择以下UIView扩展来获取它(Daniel 提供)

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

This is what I have in my UIViewController Category.这就是我的 UIViewController 类别中的内容。 Useful for many things, including getting first responder.对许多事情有用,包括获得第一响应者。 Blocks are great!积木很棒!

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

This is good candidate for recursion!这是递归的好候选! No need to add a category to UIView.无需向 UIView 添加类别。

Usage (from your view controller):用法(来自您的视图控制器):

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

Code:代码:

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

you can call privite api like this ,apple ignore:你可以像这样调用privite api,苹果忽略:

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

I would like to shared with you my implementation for find first responder in anywhere of UIView.我想与您分享我在 UIView 的任何地方查找第一响应者的实现。 I hope it helps and sorry for my english.我希望它对我的英语有所帮助和抱歉。 Thanks谢谢

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

Swift version of @thomas-müller 's response @thomas-müller回应的 Swift 版本

extension UIView {

    func firstResponder() -> UIView? {
        if self.isFirstResponder() {
            return self
        }

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

        return nil
    }

}

You can try also like this:你也可以这样试试:

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

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

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

I didn't try it but it seems a good solution我没有尝试,但它似乎是一个很好的解决方案

The solution from romeo https://stackoverflow.com/a/2799675/661022 is cool, but I noticed that the code needs one more loop. romeo https://stackoverflow.com/a/2799675/661022的解决方案很酷,但我注意到代码还需要一个循环。 I was working with tableViewController.我正在使用 tableViewController。 I edited the script and then I checked.我编辑了脚本,然后我检查了。 Everything worked perfect.一切都很完美。

I recommed to try this:我建议试试这个:

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

Code below work.下面的代码工作。

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

Update: I was wrong.更新:我错了。 You can indeed use UIApplication.shared.sendAction(_:to:from:for:) to call the first responder demonstrated in this link: http://stackoverflow.com/a/14135456/746890 .您确实可以使用UIApplication.shared.sendAction(_:to:from:for:)调用此链接中演示的第一响应者: http : //stackoverflow.com/a/14135456/746890


Most of the answers here can't really find the current first responder if it is not in the view hierarchy.如果不在视图层次结构中,这里的大多数答案都无法真正找到当前的第一响应者。 For example, AppDelegate or UIViewController subclasses.例如, AppDelegateUIViewController子类。

There is a way to guarantee you to find it even if the first responder object is not a UIView .即使第一响应者对象不是UIView ,也有一种方法可以保证您找到它。

First lets implement a reversed version of it, using the next property of UIResponder :首先让我们使用UIRespondernext属性实现它的反向版本:

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

With this computed property, we can find the current first responder from bottom to top even if it's not UIView .有了这个计算属性,我们可以从下到上找到当前的第一响应者,即使它不是UIView For example, from a view to the UIViewController who's managing it, if the view controller is the first responder.例如,从view到管理它的UIViewController ,如果视图控制器是第一响应者。

However, we still need a top-down resolution, a single var to get the current first responder.然而,我们仍然需要一个自上而下的分辨率,一个单一的var来获取当前的第一响应者。

First with the view hierarchy:首先是视图层次结构:

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

This will search for the first responder backwards, and if it couldn't find it, it would tell its subviews to do the same thing (because its subview's next is not necessarily itself).这将向后搜索第一响应者,如果找不到,它会告诉它的子视图做同样的事情(因为它的子视图的next不一定是它自己)。 With this we can find it from any view, including UIWindow .有了这个,我们可以从任何视图中找到它,包括UIWindow

And finally, we can build this:最后,我们可以构建这个:

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

So when you want to retrieve the first responder, you can call:所以当你想检索第一响应者时,你可以调用:

let firstResponder = UIResponder.first

Simplest way to find first responder:找到第一响应者的最简单方法:

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

The default implementation dispatches the action method to the given target object or, if no target is specified, to the first responder.默认实现将操作方法​​分派给给定的目标对象,或者如果没有指定目标,则分派给第一响应者。

Next step:下一步:

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

Usage: Just get UIResponder.actualFirst for your own purposes.用法:只需为您自己的目的获取UIResponder.actualFirst

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM