我在这里看到过帖子建议如果子视图成为第一响应者,UIScrollViews
应该自动滚动;UITextField
但是,我不知道如何让它工作。
我所拥有的是一个UIViewController
有一个UIScrollView
并且在其中UIScrollView
有多个文本字段。
如果需要,我知道如何手动执行此操作;但是,从我一直在阅读的内容来看,似乎可以让它自动滚动。请帮忙。
我在这里看到过帖子建议如果子视图成为第一响应者,UIScrollViews
应该自动滚动;UITextField
但是,我不知道如何让它工作。
我所拥有的是一个UIViewController
有一个UIScrollView
并且在其中UIScrollView
有多个文本字段。
如果需要,我知道如何手动执行此操作;但是,从我一直在阅读的内容来看,似乎可以让它自动滚动。请帮忙。
我希望这个例子能帮助你你可以通过这段代码滚动到任何一点。
scrollView.contentOffset = CGPointMake(0,0);
所以如果你有文本字段,它必须在视图中有一些 x,y 位置,所以你可以使用
CGPoint point = textfield.frame.origin ;
scrollView.contentOffset = point
这应该可以解决问题,
但是如果你不知道什么时候调用这段代码,那么你应该学习UITextFieldDelegate方法
在您的代码中实现此方法
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// Place Scroll Code here
}
我希望你知道如何使用委托方法。
我知道这个问题已经得到解答,但我想我会分享我从@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];
}
我还使用了动画方法,它使过渡更加平滑。
这是@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。
您无需手动执行任何操作。这是默认行为。关于您看不到该行为的原因有两种可能性
UITextField
. 解决方法见下文UIScrollView
在视图层次结构中的某个位置UITextField
与UIScrollView
您想要自动滚动的之间。这不太可能,但仍然会导致问题。对于 #1,您希望实现类似于 Apple 建议的移动位于键盘下方的内容。请注意,Apple 提供的代码不考虑轮换。要改进他们的代码,请查看这篇博客文章keyboardDidShow
中使用窗口正确转换键盘框架的方法的实现。
- (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];
}
您可以将此功能用于 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 向这个开发者致敬,你需要试试这个。
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)
}
}
如果您有多个文本字段,请说Textfield1
, Textfield2
,Textfield3
并且您想在 textfield2 成为第一响应者时沿 y 轴滚动滚动视图:
if([Textfield2 isFirstResponder])
{
scrollView.contentOffset = CGPointMake(0,yourY);
}
正如 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];
}
在UITextField
for 即将开始编辑的委托方法中,只需设置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 修复了他们的错误,它似乎仍然可以工作(手指交叉)。
基于 Vasily Bodnarchuk 的回答,我创建了一个带有简单协议的要点,您可以实施该协议,它会为您完成所有工作。您需要做的就是调用 registerAsTextDisplacer()
我在我的项目中创建了一个 BaseViewController 并实现了它
https://gist.github.com/CameronPorter95/cb68767f5f8052fdc70293c167e9430e
我看到的其他解决方案,让您将偏移量设置为原点,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)
}