当 UITextField 成为第一响应者时禁用 UIScrollView 滚动

Posted

技术标签:

【中文标题】当 UITextField 成为第一响应者时禁用 UIScrollView 滚动【英文标题】:Disable UIScrollView scrolling when UITextField becomes first responder 【发布时间】:2011-06-02 21:52:50 【问题描述】:

当嵌入在UIScrollView 中的UITextField 成为第一响应者时(例如,通过用户输入某些字符),UIScrollView 会自动滚动到该字段。有什么办法可以禁用吗?

Duplicate rdar://16538222

【问题讨论】:

您使用的是UIViewController 还是UITableViewController?对于后者,这是标准且通常需要的行为。 好吧,我正在使用嵌入在 UIScrollView 中的 UITextField。我正在模拟 SMS 应用程序“To”字段,所以每当用户输入一个字母时,我都会滚动到正在编辑的行(通过设置 UIScrollView 的 contentOffset)——如果你知道的话,就像在 Three20 中一样。发生的事情是,当用户键入一个字母时,UIScrollView 滚动到编辑行,使 UITextField 的原点超出了 scrollView 的 contentSize,然后当用户键入另一个字母时,它向上滚动到 UITextField 的原点(SCrollView 的默认行为),所以我得到这个上下行为 我同意,应该有办法手动禁用它,但我也找不到。由于这种行为,我发现自己与滚动系统作斗争。 【参考方案1】:

基于Moshe's answer... 子类UIScrollView 并覆盖以下方法:

- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated

留空。大功告成!


在斯威夫特中:

class CustomScrollView: UIScrollView 
    override func scrollRectToVisible(_ rect: CGRect, animated: Bool)  

【讨论】:

这里提到了这个解决方案:iphonedevsdk.com/forum/iphone-sdk-development/… 简单优雅的解决方案。干杯!! 这意味着如果您希望手动使用 ScrollRectToVisible 方法进行任何操作,它将无法使用。如果您不想这样做,这是一个很好的解决方案。 使用此解决方法后,我开始在 Firebase 上收到“CALayer bounds contains NaN”错误报告。【参考方案2】:

我一直在为同样的问题苦苦挣扎,终于找到了解决方案。

我通过跟踪调用跟踪调查了自动滚动是如何完成的,发现当在UITextField 中输入字母时会调用内部[UIFieldEditor scrollSelectionToVisible]。此方法似乎作用于UITextField 的最近祖先的UIScrollView

因此,在textFieldDidBeginEditing 上,通过将UITextField 包装为具有相同大小的新UIScrollView(即,在UITextField 和它的超级视图之间插入视图),这将阻止自动滚屏。最后在textFieldDidEndEditing 上移除这个包装器。

代码如下:

- (void)textFieldDidBeginEditing:(UITextField*)textField   
    UIScrollView *wrap = [[[UIScrollView alloc] initWithFrame:textField.frame] autorelease];  
    [textField.superview addSubview:wrap];  
    [textField setFrame:CGRectMake(0, 0, textField.frame.size.width, textField.frame.size.height)]; 
    [wrap addSubview: textField];  
  

- (void)textFieldDidEndEditing:(UITextField*)textField   
  UIScrollView *wrap = (UIScrollView *)textField.superview;  
  [textField setFrame:CGRectMake(wrap.frame.origin.x, wrap.frame.origin.y, wrap.frame.size.width, textField.frame.size.height)];
  [wrap.superview addSubview:textField];  
  [wrap removeFromSuperview];  
  

希望这会有所帮助!

【讨论】:

请注意:textFieldDidBeginEditing 应该是 textFieldShouldBeginEditing 否则它工作正常 +1 不是最干净的解决方案,但肯定会引导正确的方向:) 天哪。武藤,非常感谢你!和 Rabih,非常感谢您的提问!我永远也想不通。如果可以的话,我会给这 100 个赞。 赢了!这一直困扰着我整个星期(并且,在这个项目的早期版本中,一年多了)。谢谢。正如在其他 cmets 中所指出的,我发现将文本字段 always 包裹在相同大小的 UIScrollView 中更简单——不需要交换视图。 这对使用 ios 5.1 的任何人都有效吗?我似乎无法使窍门起作用!【参考方案3】:

我在禁用 UITextView 的自动滚动作为 UITableView 的单元格时遇到了同样的问题。我能够使用以下方法解决它:

@interface MyTableViewController : UITableViewController<UITextViewDelegate>

@implementation MyTableViewController 
    BOOL preventScrolling;
    // ...


// ... set self as the delegate of the text view

- (void)textViewDidBeginEditing:(UITextView *)textView 
    preventScrolling = YES;


- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
    if (preventScrolling) 
        [self.tableView setContentOffset:CGPointMake(0, -self.tableView.contentInset.top) animated:NO];
    


- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 
    preventScrolling = NO;

定义scrollViewWillBeginDragging用于恢复默认的滚动行为,当用户自己发起滚动时。

【讨论】:

感谢分享//这很好 - 鉴于项目限制,这个注入滚动视图的问题的其他解决方案对我不起作用 - 非常感谢【参考方案4】:

正如 Taketo 所提到的,当 UITextField 成为第一响应者时,其第一个类型为 UIScrollView(如果存在)的父视图会滚动以使 UITextField 可见。最简单的技巧是将每个 UITextField 简单地包装在 UIScrollView 中(或者理想情况下,将它们全部包装在一个虚拟 UIScrollView 中)。这与 Taketo 的解决方案非常相似,但它应该会给您带来更好的性能,并且在我看来它会让您的代码(或 Interface Builder 中的界面)更加干净。

【讨论】:

使用TPKeyboardAvoidingScrollView 对我有用。谢谢! 很好的解决方法!我们可以使用一个 contentView 来保存 textFields,这个 contentView 应该是 scrollView 但不能滚动。【参考方案5】:

基于 Luke 的回答,为了处理他的解决方案完全禁用自动滚动的问题,您可以选择性地禁用它,如下所示:

//  TextFieldScrollView
#import <UIKit/UIKit.h>

@interface TextFieldScrollView : UIScrollView

@property (assign, nonatomic) IBInspectable BOOL preventAutoScroll;

@end

@implementation TextFieldScrollView

- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated 
    if (self.preventAutoScroll == NO) 
        [super scrollRectToVisible:rect animated:animated];
    


@end

这样,您可以在 Interface Builder 中完全设置它以禁用自动滚动,但可以随时完全控制以重新启用它(尽管您想要这样做的原因超出了我的范围)。

【讨论】:

【参考方案6】:

看起来像包含 UITextfield 的 UIScrollview,自动调整其内容偏移量;当文本字段将成为第一响应者时。 这可以通过首先在相同大小的滚动视图中添加文本字段,然后添加到主滚动视图来解决。而不是直接添加到主滚动视图中

    // Swift

    let rect = CGRect(x: 0, y: 0, width: 200, height: 50)

    let txtfld = UITextField()
    txtfld.frame = CGRect(x: 0, y: 0, width: rect.width, height: rect.height)

    let txtFieldContainerScrollView = UIScrollView()
    txtFieldContainerScrollView.frame = rect
    txtFieldContainerScrollView.addSubview(txtfld)
    // Now add this txtFieldContainerScrollView in desired UITableViewCell, UISCrollView.. etc
    self.mainScrollView.addSubview(txtFieldContainerScrollView)

    // Am33T

【讨论】:

【参考方案7】:

这就是我的做法:

这很简单,你可以为任何 scrollRectToVisible 返回你自己的 contentOffset。

这样您就不会损害正常的行为和流程 - 只需在同一渠道中提供相同的功能,并进行您自己的改进。

#import <UIKit/UIKit.h>

@protocol ExtendedScrollViewDelegate <NSObject>

- (CGPoint)scrollView:(UIScrollView*)scrollView offsetForScrollingToVisibleRect:(CGRect)rect;

@end

@interface ExtendedScrollView : UIScrollView

@property (nonatomic, unsafe_unretained) id<ExtendedScrollViewDelegate> scrollToVisibleDelegate;

@end

#import "ExtendedScrollView.h"

@implementation ExtendedScrollView

- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated

    if (_scrollToVisibleDelegate && [_scrollToVisibleDelegate respondsToSelector:@selector(scrollView:offsetForScrollingToVisibleRect:)])
    
        [self setContentOffset:[_scrollToVisibleDelegate scrollView:self offsetForScrollingToVisibleRect:rect] animated:animated];
    
    else
    
        [super scrollRectToVisible:rect animated:animated];
    


@end

【讨论】:

【参考方案8】:

我尝试过@TaketoSano 的回答,但似乎不起作用。我的情况是我没有滚动视图,只有一个包含多个文本字段的视图。

最后,我找到了解决方法。我需要两个默认的键盘通知名称:

UIKeyboardDidShowNotification 当键盘出现时; UIKeyboardWillHideNotification 当键盘隐藏时。

这是我使用的示例代码:

- (void)viewDidLoad 
  [super viewDidLoad];

  ...

  NSNotificationCenter * notificationCetner = [NSNotificationCenter defaultCenter];
  [notificationCetner addObserver:self
                         selector:@selector(_keyboardWasShown:)
                             name:UIKeyboardDidShowNotification
                           object:nil];
  [notificationCetner addObserver:self
                         selector:@selector(_keyboardWillHide:)
                             name:UIKeyboardWillHideNotification
                           object:nil];


- (void)_keyboardWasShown:(NSNotification *)note 
  [self.view setFrame:(CGRect)272.f, 55.f, 480.f, 315.f];


- (void)_keyboardWillHide:(NSNotification *)note 
  [self.view setFrame:(CGRect)272.f, 226.5f, 480.f, 315.f];

这里,(CGRect)272.f, 226.5f, 480.f, 315.f 是隐藏键盘时视图的默认框架。而(CGRect)272.f, 55.f, 480.f, 315.f 是键盘显示时的视图框架。

顺便说一句,视图的帧变化会自动应用动画,这真的很完美!

【讨论】:

【参考方案9】:

我有一个集合视图,顶部有一个文本字段,模仿 UITableView.tableHeaderView。此文本字段位于负内容偏移空间中,因此它不会干扰集合视图的其余部分。我基本上是在检测用户是否在滚动视图中执行滚动,以及文本字段是否是第一响应者,以及滚动视图是否被滚动到滚动视图内容插图的顶部之外。这个确切的代码不一定能帮助任何人,但他们可以操纵它以适应他们的情况。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 

    // This is solving the issue where making the text field first responder
    // automatically scrolls the scrollview down by the height of the search bar.
    if (!scrollView.isDragging && !scrollView.isDecelerating &&
        self.searchField.isFirstResponder &&
        (scrollView.contentOffset.y < -scrollView.contentInset.top)) 

        [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, -scrollView.contentInset.top) animated:NO];
    

【讨论】:

【参考方案10】:

我不知道UIScrollView 的任何属性会允许这样做。恕我直言,能够禁用该功能将是糟糕的用户体验。

也就是说,可以继承 UIScrollView 并覆盖它的一些方法,以在滚动之前检查 UITextfield 是否不是第一响应者。

【讨论】:

它没有用,您能否详细查看我对 Ortwin 的回答的评论?【参考方案11】:

当您选择 textField 时停止滚动视图滚动的更简单方法是在 viewController::viewWillAppear() 中不要调用 [super viewWillAppear];

然后您可以随意控制滚动。

【讨论】:

您将在此处不调用超类实现,从而为您的应用引入很多 奇怪的行为。我建议不要这样做。

以上是关于当 UITextField 成为第一响应者时禁用 UIScrollView 滚动的主要内容,如果未能解决你的问题,请参考以下文章

UITextField 成为第一响应者时自动大写键盘

UITextField 不能成为 UITableView 中的第一响应者

UITextField, becomeFirstResponder, resignFirstResponder

为啥 uitextfield 在成为第一响应者时从不返回 nil?

当我使 UIAlertView 中的 UITextField 成为第一响应者时,为啥键盘不显示?

使 UITextField 子视图成为 UITableView 中的第一响应者时出错