UIPageViewController:控制器之间滚动时键盘卡住

时间:2018-09-19 16:36:23

标签: ios swift uitextfield uipageviewcontroller

我有一个带有多个VC的PageViewController。这些VC有一个UITextField,它成为viewDidAppear上的firstResponder。一切都包裹在NavigationController中。

  - NavigationController
  --- MainViewController
  ----- PageViewController
  ------- ViewController
  --------- ScrollView
  ----------- UITextField

前进很好。但是倒退,我遇到了一个使我发疯的错误:键盘随机停留。 TextFields位于滚动视图中,该视图可观察键盘并进行滚动以避免文本字段被键盘隐藏。

导航/滚动的工作方式如下:每个VC都有一个“下一步”按钮。要向后导航,导航栏中有一个“后退”按钮。

一些代码。 基本VC:

import UIKit
import SnapKit
import RxSwift
import CLCarRentalCore

class DigitalBookingViewController: DigitalBookingBaseViewController {

    // MARK: Interface Properties
    internal let scrollView = UIScrollView()
    internal let stackView = UIStackView()
    internal let spacingView = View()
    internal let nextButton = Button()

    // MARK: Internal Properties
    internal var editingTextField: UITextField?
    internal let keyboardObserver = KeyboardObserver()
    internal var observer: NSObjectProtocol?
    internal let disposeBag = DisposeBag()

    internal lazy var user: DigitalBookingUser  = {
        Defaults.digitalBookingUser ?? DigitalBookingUser()
    }()

    internal func setupLabel(label: Label) {
        label.font = Fonts.digitalBookingFieldLabel
        label.textColor = .grey1
        label.textAlignment = .left
    }

    internal func addView(_ view: UIView) {
        stackView.addArrangedSubview(view)
        setWidth(view: view)
    }

    private func setWidth(view: UIView) {
        view.snp.makeConstraints { make in
            make.width.equalToSuperview().priority(999)
        }
    }
}

// MARK: - Actions
extension DigitalBookingViewController {
    @objc func nextScreen() {
        delegate?.didSelectNext()
    }

    func saveUser() {
        Defaults.digitalBookingUser = user
    }
}

// MARK: - Observation
extension DigitalBookingViewController {

    @objc func keyboardDidShow() {
        guard let textView = editingTextField else { return }

        let rect = textView.convert(textView.bounds, to: scrollView)
        scrollView.scrollRectToVisible(rect, animated: true)
        textView.becomeFirstResponder()
    }

}

// MARK: - UITextFieldDelegate
extension DigitalBookingViewController: UITextFieldDelegate {

    @objc internal func textFieldDidChange(_ sender: UITextField) {
        guard let text = sender.text else { return }
        text.isEmpty ? nextButton.disable() : nextButton.enable()
    }

    internal func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        guard
            let text = textField.text,
            !text.isEmpty
            else { return false }
        view.endEditing(true)
        nextScreen()
        return true
    }

    internal func textFieldDidBeginEditing(_ textField: UITextField) {
        editingTextField = textField
        textField.underlined(color: .pink)
    }

    internal func textFieldDidEndEditing(_ textField: UITextField) {
        textField.underlined(color: .grey4)
    }
}

// MARK: - Lifecycle
extension DigitalBookingViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        observer = registerKeyboardNotification(scrollView: scrollView)

        keyboardObserver.observe { [weak self] (event) -> Void in
            guard let digitalBookingViewController = self else { return }

            switch event.type {
            case .didShow:
                digitalBookingViewController.keyboardDidShow()
            default:
                break
            }
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        if let observer = self.observer {
            NotificationCenter.default.removeObserver(observer)
        }

        editingTextField?.resignFirstResponder()
        editingTextField = nil

        view.endEditing(true)
    }
}

// MARK: - Interface
extension DigitalBookingViewController {

    // MARK: Design
    override func setupDesign() {
        super.setupDesign()

        nextButton.setupDesign(with: Strings.digitalBooking_next.localized.uppercased(), style: .pink)
        nextButton.enable()
    }

    // MARK: Bindings
    override func setupBindings() {
        super.setupBindings()

        nextButton.addTarget(self, action: #selector(nextScreen), for: .touchUpInside)
        hideKeyboardWhenTappedAround()
    }

    // MARK: Layout
    override func setupLayout() {
        super.setupLayout()

        view.addSubview(scrollView)
        view.addSubview(nextButton)

        scrollView.addSubview(stackView)
        stackView.addArrangedSubview(spacingView)
        stackView.axis = .vertical
        stackView.alignment = .center

        scrollView.snp.remakeConstraints { make in
            make.top.equalTo(topLayoutGuide.snp.bottom)
            make.left.right.equalToSuperview()
        }

        stackView.snp.makeConstraints { make in
            make.edges.width.equalToSuperview().inset(Margins.large)
        }

        spacingView.snp.makeConstraints { make in
            make.height.equalTo(Margins.Spacing.small)
        }

        nextButton.snp.makeConstraints { make in
            make.height.equalTo(Sizes.buttonHeight)
            make.left.right.equalToSuperview().inset(Margins.large)
            make.bottom.equalTo(bottomLayoutGuide.snp.top).offset(-Margins.large)
            make.top.equalTo(scrollView.snp.bottom).offset(Margins.large)
        }
    }
}

子类如下:

import UIKit

class AboutMeNameViewController: DigitalBookingViewController {

// MARK: Interface Properties
private let headerLabel = SectionHeaderView()
private let nameLabel = Label()
private let nameTextField = QuestionaryTextField()

// MARK: Overrides
override func nextScreen() {
    user.name = nameTextField.text
    saveUser()

    super.nextScreen()
}
}

// MARK: - Lifecycle
extension AboutMeNameViewController {

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    nameTextField.becomeFirstResponder()
    editingTextField = nameTextField

}
}

我的PageViewController

import UIKit

class DigitalBookingPageViewController: PageViewController {

    // MARK: Private Properties
    private var allViewControllers = [[DigitalBookingBaseViewController]]()

    // MARK: Internal Properties
    internal var numberOfSteps: Int {
        return allViewControllers.flatMap { $0 }.count - 1
    }

    private var currentPage = Defaults.digitalBookingPage ?? 0
    private var currentSection = Defaults.digitalBookingSection ?? 0

    weak var mainDBDelegate: MainDigitalBookingDelegate?

    // MARK: Initializers
    init() {
        super.init(transitionStyle: .scroll, navigationOrientation: .horizontal)
        setupViewControllers()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: - Actions
extension DigitalBookingPageViewController {

    private func saveLocation() {
        Defaults.digitalBookingPage = currentPage
        Defaults.digitalBookingSection = currentSection
    }


    func scrollToNext() -> Bool {
        if currentPage + 1 == allViewControllers[currentSection].count
            && currentSection + 1 == allViewControllers.count {
            return false
        }

        currentPage = currentPage + 1 < allViewControllers[currentSection].count ? currentPage + 1 : 0
        currentSection = currentPage == 0 ? currentSection + 1 : currentSection

        let viewController = allViewControllers[currentSection][currentPage]

        setViewControllers([viewController], direction: .forward, animated: true, completion: { _ in
            self.mainDBDelegate?.didFinishAnimation()
        })
        saveLocation()
        return true
    }

    func scrollToPrevious() -> Bool{
        if currentPage == 0 && currentSection == 0 { return false }

        if currentPage - 1 < 0 {
            currentSection = currentSection - 1 < 0 ? 0 : currentSection - 1
            currentPage = allViewControllers[currentSection].count - 1
        } else {
            currentPage -= 1
        }

        let viewController = allViewControllers[currentSection][currentPage]

        setViewControllers([viewController], direction: .reverse, animated: true, completion: { _ in
            self.mainDBDelegate?.didFinishAnimation()
        })            
        saveLocation()
        return true
    }

}

// MARK: - Private Functions
private extension DigitalBookingPageViewController {

    private func setupViewControllers() {

        var generalInfoSection = [DigitalBookingBaseViewController]()
        generalInfoSection.append(AboutMeFirstViewController())
        generalInfoSection.append(AboutMeNameViewController())
        generalInfoSection.append(AboutMeSurnameViewController())
        generalInfoSection.append(AboutMeBirthdayViewController())
        generalInfoSection.append(AboutMeDrivingLicenseViewController())

        var incomeSection = [DigitalBookingBaseViewController]()
        incomeSection.append(AboutMeIncomeFirstViewController())
        incomeSection.append(AboutMeIncomeSelectorViewController())
        incomeSection.append(AboutMeExpensesViewController())
        incomeSection.append(AboutMeEmployeerViewController())
        incomeSection.append(AboutMeIncomeFinalViewController())

        var contactInfoSection = [DigitalBookingBaseViewController]()
        contactInfoSection.append(ContactInformationViewController())
        contactInfoSection.append(ContactInformationBankViewController())
        contactInfoSection.append(ContactInformationLoginViewController())
        contactInfoSection.append(ContactInformationVerifyEmailViewController())
        contactInfoSection.append(ContactInformationAutoReserved())

        allViewControllers.append(contentsOf: [generalInfoSection, incomeSection, contactInfoSection])
        allViewControllers.forEach { $0.forEach { $0.delegate = self } }

        let firstViewController = allViewControllers[currentSection][currentPage]
        setViewControllers([firstViewController], direction: .forward, animated: true)
    }
}

// MARK: - DigitalBookingPageControllerDelegate
extension DigitalBookingPageViewController: DigitalBookingPageControllerDelegate {

    func didSelectNext() {
        mainDBDelegate?.didMoveToNextPage()
    }

    func didFinishDigitalBooking() {
        mainDBDelegate?.didFinishDigitalBooking()
    }
}

最后是MainViewController推动的NavigationController

import MessageUI

protocol DigitalBookingMainViewControllerDelegate: class {
    func didFinishDigitalBooking(_ controller: DigitalBookingMainViewController)
}

class DigitalBookingMainViewController: ViewController {

    // MARK: Interface Properties
    private var progressView: ProgressView?
    private let questionaryPageViewController = DigitalBookingPageViewController()

    weak var delegate: DigitalBookingMainViewControllerDelegate?

    // MARK: Initializers
    init() {
        super.init(nibName: nil, bundle: nil)
        progressView = ProgressView(numberOfSteps: questionaryPageViewController.numberOfSteps)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: - Actions
extension DigitalBookingMainViewController {

    @objc func nextScreen() {
            if questionaryPageViewController.scrollToNext() { progressView?.increaseProgress() }
    }

    func previousScreen() {
            if questionaryPageViewController.scrollToPrevious() { progressView?.decreaseProgress() }
    }

    @objc func dismissViewController() {
        dismiss(animated: true)
    }

}

// MARK: - DigitalBookingDelegate
extension DigitalBookingMainViewController: MainDigitalBookingDelegate {

    @objc func didMoveToPreviousPage() {
        UIApplication.shared.windows.first?.endEditing(true)
        navigationItem.leftBarButtonItem?.isEnabled = false
        progressView?.decreaseProgress()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [unowned self] in
            self.previousScreen()
        }
    }

    @objc func didMoveToNextPage() {
        UIApplication.shared.windows.first?.endEditing(true)
        progressView?.increaseProgress()
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [unowned self] in
            self.nextScreen()
        }
    }

    func hideProgressBar() {
        progressView?.isHidden = true
    }

    func showProgressBar() {
        progressView?.isHidden = false
    }

    func didFinishDigitalBooking() {
        delegate?.didFinishDigitalBooking(self)
        self.navigationController?.dismiss(animated: true)
    }

    func didFinishAnimation() {
        navigationItem.leftBarButtonItem?.isEnabled = true
    }

}

// MARK: - Interface
extension DigitalBookingMainViewController {

    // MARK: Design
    override func setupDesign() {
        super.setupDesign()

        navigationController?.navigationBar.tintColor = .pink
    }

    // MARK: Strings
    override func setupStrings() {
        super.setupStrings()

        title = Strings.digitalBooking_booking.localized
    }

    // MARK: Bindings
    override func setupBindings() {
        super.setupBindings()

        questionaryPageViewController.mainDBDelegate = self
        addChildViewController(questionaryPageViewController)
        questionaryPageViewController.didMove(toParentViewController: self)

        // TODO: Fix navigation bug
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: Strings.digitalBooking_back.localized, style: .plain, target: self, action: #selector(didMoveToPreviousPage))
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: Strings.digitalBooking_cancel.localized, style: .plain, target: self, action: #selector(dismissViewController))
    }

    // MARK: Layout
    override func setupLayout() {
        super.setupLayout()

        guard let progressView = progressView else { return }

        view.addSubview(progressView)
        view.addSubview(questionaryPageViewController.view)

        progressView.snp.makeConstraints { make in
            make.top.equalTo(topLayoutGuide.snp.bottom)
            make.left.right.equalToSuperview()
            make.height.equalTo(Sizes.progressBarHeight)
        }

        questionaryPageViewController.view.snp.makeConstraints { make in
            make.top.equalTo(progressView.snp.bottom)
            make.left.right.equalToSuperview()
            make.bottom.equalToSuperview()
        }

    }
}

0 个答案:

没有答案