我看到here周围的帖子表明如果子视图UIScrollViews
成为第一个响应者,UITextField
应自动滚动;但是,我无法弄清楚如何让它发挥作用。
我所拥有的UIViewController
UIScrollView
UIScrollView
且{{1}}内有多个文本字段。
我知道如何在必要时手动执行此操作;但是,从我一直在阅读的内容来看,似乎可以让它自动滚动。请帮助。
答案 0 :(得分:32)
我希望这个例子可以帮到你 您可以通过此代码滚动到任意点。
scrollView.contentOffset = CGPointMake(0,0);
因此,如果你有文本字段,它必须在视图上有一些x,y位置,所以你可以使用
CGPoint point = textfield.frame.origin ;
scrollView.contentOffset = point
这应该可以解决问题,
但如果您不知道何时调用此代码,那么您应该学习 UITextFieldDelegate 方法
在代码中实现此方法
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// Place Scroll Code here
}
我希望你知道如何使用委托方法。
答案 1 :(得分:11)
我知道这个问题已经得到了解答,但我想我会分享我在@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];
}
我还使用了动画方法,它可以实现更平滑的过渡。
答案 2 :(得分:6)
您无需手动操作。这是默认行为。有两种可能性,为什么你没有看到行为
UITextField
。请参阅下面的解决方案UIScrollView
和UITextField
之间的视图层次结构中的某处有另一个UIScrollView
。这种可能性较小,但仍可能导致问题。对于#1,您希望实现类似于Apple对Moving Content That Is Located Under the Keyboard的建议。请注意,Apple提供的代码不考虑轮换。有关其代码的改进,请查看使用该窗口正确翻译键盘框架的keyboardDidShow
方法的this blog post's实现。
答案 3 :(得分:4)
- (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];
}
答案 4 :(得分:3)
您可以将此功能用于UITextField的autoScroll
on 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 感谢开发者,你需要尝试这个。
答案 5 :(得分:2)
以下是@ 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并将textfields的委托设置为self。
答案 6 :(得分:1)
如果您有多个文本字段说Textfield1
,Textfield2
,Textfield3
,并且您希望在textfield2成为第一响应者时沿y轴滚动滚动视图:
if([Textfield2 isFirstResponder])
{
scrollView.contentOffset = CGPointMake(0,yourY);
}
答案 7 :(得分:1)
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)
}
}
答案 8 :(得分:0)
正如Michael McGuire在上面的第2点中提到的那样,当滚动视图在文本字段和滚动视图之间包含另一个滚动视图时,系统的默认行为不当。我发现,当仅在文本字段的 next 下有一个滚动视图时,也会发生不当行为(两者都嵌入在滚动视图中,需要进行调整,以便在文本字段出现时可以看到文本字段想开始编辑。这是在iOS 12.1上。
但是我的解决方案与上述不同。在子类的顶层滚动视图中,因此我可以添加属性和覆盖方法,因此我覆盖scrollRectToVisible:animated:
。除非有一个属性集告诉它调整传入的[super scrollRectToVisible:animated:]
(即文本字段的框架),否则它仅调用其rect
。当该属性为非nil时,它是对所讨论的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];
}
在即将开始编辑的UITextField
的委托方法中,只需将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修复了他们的错误,看来它仍然可以工作(手指交叉)。
答案 9 :(得分:0)
基于Vasily Bodnarchuk的回答,我创建了一个具有简单协议的要点,您可以实现该协议,它将为您完成所有工作。 您需要做的就是调用registerAsTextDisplacer()
我在项目中创建了一个BaseViewController并实现了它
https://gist.github.com/CameronPorter95/cb68767f5f8052fdc70293c167e9430e
答案 10 :(得分: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.height
值中减去maxY
。
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)
}