39

我在这里看到过帖子建议如果子视图成为第一响应者,UIScrollViews应该自动滚动;UITextField但是,我不知道如何让它工作。

我所拥有的是一个UIViewController有一个UIScrollView并且在其中UIScrollView有多个文本字段。

如果需要,我知道如何手动执行此操作;但是,从我一直在阅读的内容来看,似乎可以让它自动滚动。请帮忙。

4

12 回答 12

44

我希望这个例子能帮助你你可以通过这段代码滚动到任何一点。

scrollView.contentOffset = CGPointMake(0,0);

所以如果你有文本字段,它必须在视图中有一些 x,y 位置,所以你可以使用

CGPoint point = textfield.frame.origin ;
scrollView.contentOffset = point 

这应该可以解决问题,

但是如果你不知道什么时候调用这段代码,那么你应该学习UITextFieldDelegate方法

在您的代码中实现此方法

- (void)textFieldDidBeginEditing:(UITextField *)textField {
// Place Scroll Code here
}

我希望你知道如何使用委托方法。

于 2012-10-24T21:10:17.133 回答
14

我知道这个问题已经得到解答,但我想我会分享我从@Adeel 和@Basil 答案中使用的代码组合,因为它似乎在 iOS 9 上非常适合我。

-(void)textFieldDidBeginEditing:(UITextField *)textField {

    // Scroll to the text field so that it is
    // not hidden by the keyboard during editing.
    [scroll setContentOffset:CGPointMake(0, (textField.superview.frame.origin.y + (textField.frame.origin.y))) animated:YES];
}

-(void)textFieldDidEndEditing:(UITextField *)textField {

    // Remove any content offset from the scroll
    // view otherwise the scroll view will look odd.
    [scroll setContentOffset:CGPointMake(0, 0) animated:YES];
}

我还使用了动画方法,它使过渡更加平滑。

于 2015-10-21T10:11:36.277 回答
10

这是@Supertecnoboff 答案的Swift 4 更新。它对我很有用。

func textFieldDidBeginEditing(_ textField: UITextField) {
    scroll.setContentOffset(CGPoint(x: 0, y: (textField.superview?.frame.origin.y)!), animated: true)
}

func textFieldDidEndEditing(_ textField: UITextField) {
    scroll.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
}

确保扩展 UITextFieldDelegate 并将文本字段的委托设置为 self。

于 2018-04-01T02:19:37.677 回答
8

您无需手动执行任何操作。这是默认行为。关于您看不到该行为的原因有两种可能性

  1. 最可能的原因是键盘盖住了您的UITextField. 解决方法见下文
  2. 另一种可能性是您UIScrollView在视图层次结构中的某个位置UITextFieldUIScrollView您想要自动滚动的之间。这不太可能,但仍然会导致问题。

对于 #1,您希望实现类似于 Apple 建议的移动位于键盘下方的内容。请注意,Apple 提供的代码不考虑轮换。要改进他们的代码,请查看这篇博客文章keyboardDidShow中使用窗口正确转换键盘框架的方法的实现。

于 2015-04-01T17:44:29.467 回答
5
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    CGRect rect = [textField bounds];
    rect = [textField convertRect:rect toView:self.scrollView];
    rect.origin.x = 0 ;
    rect.origin.y -= 60 ;
    rect.size.height = 400;

    [self.scrollView scrollRectToVisible:rect animated:YES];
}
于 2015-06-09T11:56:17.747 回答
3

您可以将此功能用于 UITextField 的 autoScroll

UITextFieldDelegate

- (void)textFieldDidBeginEditing:(UITextField *)textField {

[self autoScrolTextField:textField onScrollView:self.scrollView];
}




- (void) autoScrolTextField: (UITextField *) textField onScrollView: (UIScrollView *) scrollView { 
 float slidePoint = 0.0f;
float keyBoard_Y_Origin = self.view.bounds.size.height - 216.0f;
float textFieldButtomPoint = textField.superview.frame.origin.y + (textField.frame.origin.y + textField.frame.size.height);

if (keyBoard_Y_Origin < textFieldButtomPoint - scrollView.contentOffset.y) {
    slidePoint = textFieldButtomPoint - keyBoard_Y_Origin + 10.0f;
    CGPoint point = CGPointMake(0.0f, slidePoint);
    scrollView.contentOffset = point;
}

编辑:

我现在使用IQKeyboardManager 向这个开发者致敬,你需要试试这个。

于 2014-07-28T06:00:02.727 回答
2

解决方案

extension UIScrollView {
    func scrollVerticallyToFirstResponderSubview(keyboardFrameHight: CGFloat) {
        guard let firstResponderSubview = findFirstResponderSubview() else { return }
        scrollVertically(toFirstResponder: firstResponderSubview,
                         keyboardFrameHight: keyboardFrameHight, animated: true)
    }

    private func scrollVertically(toFirstResponder view: UIView,
                                  keyboardFrameHight: CGFloat, animated: Bool) {
        let scrollViewVisibleRectHeight = frame.height - keyboardFrameHight
        let maxY = contentSize.height - scrollViewVisibleRectHeight
        if contentOffset.y >= maxY { return }
        var point = view.convert(view.bounds.origin, to: self)
        point.x = 0
        point.y -= scrollViewVisibleRectHeight/2
        if point.y > maxY {
            point.y = maxY
        } else if point.y < 0 {
            point.y = 0
        }
        setContentOffset(point, animated: true)
    }
}

extension UIView {
    func findFirstResponderSubview() -> UIView? { getAllSubviews().first { $0.isFirstResponder } }
    func getAllSubviews<T: UIView>() -> [T] { UIView.getAllSubviews(from: self) as [T] }
    class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] {
        parenView.subviews.flatMap { subView -> [T] in
            var result = getAllSubviews(from: subView) as [T]
            if let view = subView as? T { result.append(view) }
            return result
        }
    }
}

完整样本

不要忘记在此处粘贴解决方案代码

import UIKit

class ViewController: UIViewController {

    private weak var scrollView: UIScrollView!
    private lazy var keyboard = KeyboardNotifications(notifications: [.willHide, .willShow], delegate: self)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let scrollView = UIScrollView()
        view.addSubview(scrollView)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        scrollView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
        scrollView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        scrollView.contentSize = CGSize(width: view.frame.width, height: 1000)
        scrollView.isScrollEnabled = true
        scrollView.indicatorStyle = .default
        scrollView.backgroundColor = .yellow
        scrollView.keyboardDismissMode = .interactive
        self.scrollView = scrollView
        
        addTextField(y: 20)
        addTextField(y: 300)
        addTextField(y: 600)
        addTextField(y: 950)
    }
    
    private func addTextField(y: CGFloat) {
        let textField = UITextField()
        textField.borderStyle = .line
        scrollView.addSubview(textField)
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: y).isActive = true
        textField.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 44).isActive = true
        textField.widthAnchor.constraint(equalToConstant: 120).isActive = true
        textField.heightAnchor.constraint(equalToConstant: 44).isActive = true
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        keyboard.isEnabled = true
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        keyboard.isEnabled = false
    }
}

extension ViewController: KeyboardNotificationsDelegate {
    func keyboardWillShow(notification: NSNotification) {
        guard   let userInfo = notification.userInfo as? [String: Any],
                let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
        scrollView.contentInset.bottom = keyboardFrame.height
        scrollView.scrollVerticallyToFirstResponderSubview(keyboardFrameHight: keyboardFrame.height)
    }

    func keyboardWillHide(notification: NSNotification) {
        scrollView.contentInset.bottom = 0
    }
}

/// Solution

extension UIScrollView {
    func scrollVerticallyToFirstResponderSubview(keyboardFrameHight: CGFloat) {
        guard let firstResponderSubview = findFirstResponderSubview() else { return }
        scrollVertically(toFirstResponder: firstResponderSubview,
                         keyboardFrameHight: keyboardFrameHight, animated: true)
    }

    private func scrollVertically(toFirstResponder view: UIView,
                                  keyboardFrameHight: CGFloat, animated: Bool) {
        let scrollViewVisibleRectHeight = frame.height - keyboardFrameHight
        let maxY = contentSize.height - scrollViewVisibleRectHeight
        if contentOffset.y >= maxY { return }
        var point = view.convert(view.bounds.origin, to: self)
        point.x = 0
        point.y -= scrollViewVisibleRectHeight/2
        if point.y > maxY {
            point.y = maxY
        } else if point.y < 0 {
            point.y = 0
        }
        setContentOffset(point, animated: true)
    }
}

extension UIView {
    func findFirstResponderSubview() -> UIView? { getAllSubviews().first { $0.isFirstResponder } }
    func getAllSubviews<T: UIView>() -> [T] { UIView.getAllSubviews(from: self) as [T] }
    class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] {
        parenView.subviews.flatMap { subView -> [T] in
            var result = getAllSubviews(from: subView) as [T]
            if let view = subView as? T { result.append(view) }
            return result
        }
    }
}

// https://stackoverflow.com/a/42600092/4488252

import Foundation

protocol KeyboardNotificationsDelegate: class {
    func keyboardWillShow(notification: NSNotification)
    func keyboardWillHide(notification: NSNotification)
    func keyboardDidShow(notification: NSNotification)
    func keyboardDidHide(notification: NSNotification)
}

extension KeyboardNotificationsDelegate {
    func keyboardWillShow(notification: NSNotification) {}
    func keyboardWillHide(notification: NSNotification) {}
    func keyboardDidShow(notification: NSNotification) {}
    func keyboardDidHide(notification: NSNotification) {}
}

class KeyboardNotifications {

    fileprivate var _isEnabled: Bool
    fileprivate var notifications: [NotificationType]
    fileprivate weak var delegate: KeyboardNotificationsDelegate?
    fileprivate(set) lazy var isKeyboardShown: Bool = false

    init(notifications: [NotificationType], delegate: KeyboardNotificationsDelegate) {
        _isEnabled = false
        self.notifications = notifications
        self.delegate = delegate
    }

    deinit { if isEnabled { isEnabled = false } }
}

// MARK: - enums

extension KeyboardNotifications {

    enum NotificationType {
        case willShow, willHide, didShow, didHide

        var selector: Selector {
            switch self {
                case .willShow: return #selector(keyboardWillShow(notification:))
                case .willHide: return #selector(keyboardWillHide(notification:))
                case .didShow: return #selector(keyboardDidShow(notification:))
                case .didHide: return #selector(keyboardDidHide(notification:))
            }
        }

        var notificationName: NSNotification.Name {
            switch self {
                case .willShow: return UIResponder.keyboardWillShowNotification
                case .willHide: return UIResponder.keyboardWillHideNotification
                case .didShow: return UIResponder.keyboardDidShowNotification
                case .didHide: return UIResponder.keyboardDidHideNotification
            }
        }
    }
}

// MARK: - isEnabled

extension KeyboardNotifications {

    private func addObserver(type: NotificationType) {
        NotificationCenter.default.addObserver(self, selector: type.selector, name: type.notificationName, object: nil)
    }

    var isEnabled: Bool {
        set {
            if newValue {
                for notificaton in notifications { addObserver(type: notificaton) }
            } else {
                NotificationCenter.default.removeObserver(self)
            }
            _isEnabled = newValue
        }

        get { _isEnabled }
    }

}

// MARK: - Notification functions

extension KeyboardNotifications {

    @objc func keyboardWillShow(notification: NSNotification) {
        delegate?.keyboardWillShow(notification: notification)
        isKeyboardShown = true
    }

    @objc func keyboardWillHide(notification: NSNotification) {
        delegate?.keyboardWillHide(notification: notification)
        isKeyboardShown = false
    }

    @objc func keyboardDidShow(notification: NSNotification) {
        isKeyboardShown = true
        delegate?.keyboardDidShow(notification: notification)
    }

    @objc func keyboardDidHide(notification: NSNotification) {
        isKeyboardShown = false
        delegate?.keyboardDidHide(notification: notification)
    }
}
于 2020-07-29T20:33:58.190 回答
1

如果您有多个文本字段,请说Textfield1, Textfield2Textfield3并且您想在 textfield2 成为第一响应者时沿 y 轴滚动滚动视图:

if([Textfield2 isFirstResponder])
{
    scrollView.contentOffset = CGPointMake(0,yourY);
} 
于 2012-10-24T23:13:20.243 回答
0

正如 Michael McGuire 在上面的第 2 点中提到的,当滚动视图在文本字段和滚动视图之间包含另一个滚动视图时,系统的默认行为会出现异常。我发现当仅在文本字段旁边有一个滚动视图时也会发生不当行为(两者都嵌入在滚动视图中,需要调整以在文本字段想要开始编辑时将文本字段显示在视图中。这是在 iOS 12.1 上。

但我的解决方案与上述不同。在我的顶级滚动视图中,它是子类的,因此我可以添加属性和覆盖方法,我覆盖scrollRectToVisible:animated:. 它只是调用它[super scrollRectToVisible:animated:],除非有一个属性集告诉它调整rect传入的值,即文本字段的框架。当属性为非零时,它是对有UITextField问题的引用,并且rect调整了 ,以便滚动视图比系统认为的更远。所以我把它放在UIScrollView的子类头文件中:

@property (nullable) UITextField *textFieldToBringIntoView;

(在实现中适当@synthesize textFieldToBringIntoView;。然后我将此覆盖方法添加到实现中:

- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)how
{
    if (textFieldToBringIntoView) {
       // Do whatever mucking with `rect`'s origin needed to make it visible
       // based on context or its spatial relationship with the other
       // view that the system is getting confused by.

       textFieldToBringIntoView = nil;        // Go back to normal
       }
    [super scrollRectToVisible:rect animated:how];
}

UITextFieldfor 即将开始编辑的委托方法中,只需设置textFieldToBringIntoView为有textField问题的:

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
    // Ensure it scrolls into view so that keyboard doesn't obscure it
    // The system is about to call |scrollRectIntoView:| for the scrolling
    // superview, but the system doesn't get things right in certain cases.

    UIScrollView *parent = (UIScrollView *)textField.superview;
    // (or figure out the parent UIScrollView some other way)

    // Tell the override to do something special just once
    // based on this text field's position in its parent's scroll view.
    parent.textFieldToBringIntoView = textField;
    // The override function will set this back to nil

    return(YES);
}

它似乎工作。如果 Apple 修复了他们的错误,它似乎仍然可以工作(手指交叉)。

于 2019-01-09T06:39:25.937 回答
0

基于 Vasily Bodnarchuk 的回答,我创建了一个带有简单协议的要点,您可以实施该协议,它会为您完成所有工作。您需要做的就是调用 registerAsTextDisplacer()

我在我的项目中创建了一个 BaseViewController 并实现了它

https://gist.github.com/CameronPorter95/cb68767f5f8052fdc70293c167e9430e

于 2020-09-04T08:04:15.047 回答
0

我看到的其他解决方案,让您将偏移量设置为原点,textField但这会使滚动视图超出其范围。我对偏移进行了此调整,而不是超出底部或顶部偏移。

将 设置keyboardHeightConstraint为页面底部。当键盘显示时,将其约束的常量更新为负键盘高度。然后滚动到responderField我们将在下面显示的。

@IBOutlet var keyboardHeightConstraint: NSLayoutConstraint?
var responderField: String?

@objc func keyboardNotification(notification: NSNotification) {
     guard let keyboardValue = notification.userInfo [UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
     let keyboardHeight = keyboardValue.cgRectValue.height

     keyboardHeightConstraint?.constant = -keyboardHeight
     scroll(field: responderField!)
}

func textFieldDidBeginEditing(_ textField: UITextField) {
     responderField = textField
}

现在我们要确保滚动不大于底部偏移量也不小于顶部偏移量。同时,我们要计算字段maxY值的偏移量。为此,我们scrollView.bounds.size.heightmaxY值中减去 。

let targetOffset = field.frame.maxY - scrollView.bounds.size.height

我发现滚动键盘高度的额外距离会更好,但如果你想在字段的正下方滚动,你可以忽略这一点。

let targetOffset = keyboardHeight + field.frame.maxY - scrollView.bounds.size.height

scrollView.contentInset.bottom如果标签栏可见,请记住添加。

func scroll(field: UITextField) {
        guard let keyboardConstraintsConstant = keyboardHeightConstraint?.constant else { return }
        let keyboardHeight = -keyboardConstraintsConstant
        
        view.layoutIfNeeded()
        let bottomOffset = scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom
        let topOffset = -scrollView.safeAreaInsets.top
        let targetOffset = keyboardHeight + field.frame.maxY + scrollView.contentInset.bottom - scrollView.bounds.size.height
        let adjustedOffset = targetOffset > bottomOffset ? bottomOffset : (targetOffset < topOffset ? topOffset : targetOffset)
        scrollView.setContentOffset(CGPoint(x: 0, y: adjustedOffset), animated: true)
}
于 2020-09-25T06:34:58.280 回答
0

如果您有 scrollView 和 tableView 与无效的 intrinsicContentSize 作为子视图,您可以在情节提要中禁用 tableView 滚动或在代码中将 tableView.isScrollEnabled 设置为 false。

故事板

于 2022-02-15T10:59:19.930 回答