UITableView.indexPathsForVisibleRows 在表视图内容偏移以避免可见键盘时不返回正确的值

问题描述

我有一个聊天视图,当消息进来时,如果最后一个单元格可见,那么表视图应该滚动到末尾,以便新消息可见。当键盘隐藏时,此行为正常工作。但是当键盘可见时,我会根据键盘的高度偏移表格视图内容,以便之前可见的最后几条消息仍然可见。但是现在当有新消息进来时,可见的 indexPaths 列表是完全错误的。因此不会触发滚动到结束条件。

let offset = -1 * endFrame.size.height
self.discussionChatView.discussionTableView.contentOffset.y -= offset

在这种情况下,滚动正常工作。当最后一个单元格可见时它滚动到最后,而当最后一个单元格不可见时它不滚动。

控制台日志:

Visible paths:  [[2,6],[2,7],8],9],10]]
Sections:  2
Row:  10
Last cell visible true
Visible paths:  [[1,14],[1,15],16]]
Sections:  2
Row:  11
Last cell visible false

但是当键盘可见时,它的工作方式就不一样了。最终的聊天视图显示在最后一张图片中(手动向下滚动后)。

来自键盘框架更改处理代码的日志:

Shifted visible paths:  Optional([[2,10],11],12]])
Shifted visible paths:  Optional([[2,12]])
Shifted visible paths:  Optional([])

来自聊天表视图的日志(实际上我已经滚动到最后一个单元格)

Visible paths:  [[2,3],4],5],8]]
Sections:  2
Row:  12
Last cell visible false

视图控制器代码

class discussionsViewController: UIViewController {
    
    static let discussionVC = discussionsViewController()
    let interactor = Interactor()
    let sideNavVC = SideNavVC()
    
    let headerContainer = UIView()
    let countryCountView = UserCountryUIView()
    let discussionsMessageBox = discussionsMessageBox()
    let discussionChatView = discussionChatView()
    let userProfileButton = UIButton()
    
    var discussionsMessageBoxBottomAnchor: NSLayoutConstraint = NSLayoutConstraint()
    var isKeyboardFullyVisible = false
    let keyboard = Keyboardobserver()
        
    let postLoginInfoMessage =  "This is a chatroom created to help students discuss topics with each other and get advice. Use it to ask questions,get tips,etc. "
    var preLoginInfoMessage = "You will have to login with your gmail account to send messages."

    
    override func viewDidLoad() {
        
        view.backgroundColor = UIColor.black
        addSlideGesture()
        addHeader()
        addCountryCountTableView()
        adddiscussionsMessageBox()
        adddiscussionChatView()
        
        preLoginInfoMessage = postLoginInfoMessage + preLoginInfoMessage
        GIDSignIn.sharedInstance().delegate = self
        GIDSignIn.sharedInstance()?.presentingViewController = self

        keyboard.observe { [weak self] (event) -> Void in
            guard let self = self else { return }
            switch event.type {
            case .willChangeFrame:
                self.handleKeyboardWillChangeFrame(keyboardEvent: event)
            default:
                break
            }
        }

        
    }
    
    deinit {
        //        NotificationCenter.default.removeObserver(self)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        discussionChatView.scrollTableViewToEnd(animated: true)
    }
    
    
    func addHeader() {
        
        headerContainer.translatesAutoresizingMaskIntoConstraints = false
        let discussionsTitleLbl = UILabel()
        discussionsTitleLbl.translatesAutoresizingMaskIntoConstraints = false
        discussionsTitleLbl.text = "discussions"
        discussionsTitleLbl.textColor = .white
        discussionsTitleLbl.font = UIFont(name: "HelveticaNeue-Bold",size: 20)!
        
        let hamburgerBtn = UIButton()
        hamburgerBtn.translatesAutoresizingMaskIntoConstraints = false
        hamburgerBtn.setimage(sideNavIcon.withRenderingMode(.alwaystemplate),for: .normal)
        
        hamburgerBtn.tintColor = accentColor
        setBtnImgProp(button: hamburgerBtn,topPadding: 45/4,leftPadding: 5)
        hamburgerBtn.addTarget(self,action: #selector(displaySideNavTapped),for: .touchUpInside)
        hamburgerBtn.contentMode = .scaleAspectFit
        
        
        userProfileButton.translatesAutoresizingMaskIntoConstraints = false
        userProfileButton.setimage(userPlaceholder.withRenderingMode(.alwaysOriginal),for: .normal)
        userProfileButton.imageView?.contentMode = .scaletoFill
//        userProfileButton.tintColor = accentColor
        userProfileButton.imageEdgeInsets = UIEdgeInsets(top: 0,left: 0,bottom: 0,right: 0)
        userProfileButton.addTarget(self,action: #selector(displayInfoTapped),for: .touchUpInside)
        userProfileButton.clipsToBounds = true
        userProfileButton.layer.cornerRadius = 20
        userProfileButton.layer.borderWidth = 1
        userProfileButton.layer.borderColor = UIColor.white.cgColor
        setUserProfileImage()
        
        headerContainer.addSubview(hamburgerBtn)
        headerContainer.addSubview(discussionsTitleLbl)
        headerContainer.addSubview(userProfileButton)
        view.addSubview(headerContainer)
        
        NSLayoutConstraint.activate([
            hamburgerBtn.leadingAnchor.constraint(equalTo: headerContainer.leadingAnchor),hamburgerBtn.topAnchor.constraint(equalTo: headerContainer.topAnchor),hamburgerBtn.heightAnchor.constraint(equalToConstant: 35),hamburgerBtn.widthAnchor.constraint(equalToConstant: 35),discussionsTitleLbl.centerXAnchor.constraint(equalTo: headerContainer.centerXAnchor),discussionsTitleLbl.centerYAnchor.constraint(equalTo: headerContainer.centerYAnchor),discussionsTitleLbl.heightAnchor.constraint(equalToConstant: 50),userProfileButton.trailingAnchor.constraint(equalTo: headerContainer.trailingAnchor,constant: -4),userProfileButton.topAnchor.constraint(equalTo: headerContainer.topAnchor),userProfileButton.heightAnchor.constraint(equalToConstant: 40),userProfileButton.widthAnchor.constraint(equalToConstant: 40),headerContainer.heightAnchor.constraint(equalToConstant: 50),headerContainer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,constant: 4),headerContainer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,headerContainer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor,])
    }
    
    func addCountryCountTableView() {
        countryCountView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(countryCountView)
        
        NSLayoutConstraint.activate([
            countryCountView.leadingAnchor.constraint(equalTo: view.leadingAnchor,constant: 0),countryCountView.trailingAnchor.constraint(equalTo: view.trailingAnchor),countryCountView.topAnchor.constraint(equalTo: headerContainer.bottomAnchor),countryCountView.heightAnchor.constraint(equalToConstant: 60)
        ])
    }
    
    func adddiscussionsMessageBox() {
        view.addSubview(discussionsMessageBox)
        discussionsMessageBox.translatesAutoresizingMaskIntoConstraints = false
        
        
        discussionsMessageBoxBottomAnchor = discussionsMessageBox.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,constant: 0)
        
        NSLayoutConstraint.activate([
            discussionsMessageBox.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,constant: 10),discussionsMessageBox.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor,constant: -10),discussionsMessageBoxBottomAnchor,])
        
    }
    
    func adddiscussionChatView() {
        self.view.addSubview(discussionChatView)
        discussionChatView.translatesAutoresizingMaskIntoConstraints = false
        
        
        NSLayoutConstraint.activate([
            discussionChatView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,discussionChatView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor,discussionChatView.topAnchor.constraint(equalTo: countryCountView.bottomAnchor,discussionChatView.bottomAnchor.constraint(equalTo: discussionsMessageBox.topAnchor,])
    }
    
    func addSlideGesture() {
        
        let edgeSlide = UIPanGestureRecognizer(target: self,action: #selector(presentSideNav(sender:)))
        view.addGestureRecognizer(edgeSlide)
    }
}


//MARK:- All Actions

extension discussionsViewController {
    @objc func displaySideNavTapped(_ sender: Any) {
        Analytics.logEvent(AnalyticsEvent.ShowSideNav.rawValue,parameters: nil)
        sideNavVC.transitioningDelegate = self
        sideNavVC.modalPresentationStyle = .custom
        sideNavVC.interactor = interactor
        sideNavVC.calledFromVC = discussionsViewController.discussionVC
        self.present(sideNavVC,animated: true,completion: nil)
        
    }
    
    @objc func displayInfoTapped(_ sender: UIButton) {
        
        if GIDSignIn.sharedInstance()?.currentUser == nil {
            let preSignInAlert = UIAlertController(title: "discussions",message: preLoginInfoMessage,preferredStyle: .alert)
            let dismissAction = UIAlertAction(title: "Okay",style: .cancel) { _ in }
            let loginAction = UIAlertAction(title: "Login",style: .default) { (alert) in
                GIDSignIn.sharedInstance()?.signIn()
            }
            preSignInAlert.addAction(dismissAction)
            preSignInAlert.addAction(loginAction)
            present(preSignInAlert,completion: nil)
        } else {
            let postSignInAlert = UIAlertController(title: "discussions",message: postLoginInfoMessage,style: .cancel) { _ in }
            postSignInAlert.addAction(dismissAction)
            present(postSignInAlert,completion: nil)
        }
    }
    
    @objc func presentSideNav(sender: UIPanGestureRecognizer) {
        
        let translation = sender.translation(in: view)
        let progress = MenuHelper.calculateProgress(translationInView: translation,viewBounds: view.bounds,direction: .Right)
        
        MenuHelper.mapGestureStatetoInteractor(gestureState: sender.state,progress: progress,interactor: interactor) {
            
            sideNavVC.transitioningDelegate = self
            sideNavVC.modalPresentationStyle = .custom
            sideNavVC.interactor = interactor
            sideNavVC.calledFromVC = discussionsViewController.discussionVC
            self.present(sideNavVC,completion: nil)
            
        }
    }
}


//MARK:- Transition Delegate

extension discussionsViewController: UIViewControllerTransitioningDelegate {
    
    func animationController(forPresented presented: UIViewController,presenting: UIViewController,source: UIViewController)
    -> UIViewControllerAnimatedTransitioning?
    {
        if presenting == self && presented == sideNavVC {
            return RevealSideNav()
        }
        return nil
    }
    
    func animationController(fordismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        if dismissed == sideNavVC {
            return HideSideNav(vcPresent: true)
        }
        return nil
    }
    
    func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactor.hasstarted ? interactor : nil
    }
    
    func interactionControllerFordismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactor.hasstarted ? interactor : nil
    }
}

//MARK:- Keyboard handler

extension discussionsViewController {
    
    @objc func keyboardWillShow(notification: Notification) {
        if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
            let keyboardRectangle = keyboardFrame.cgRectValue
            let keyboardHeight = keyboardRectangle.height
            print("Keyboard Height:",keyboardHeight)
        }
    }
    
    func keyboardWillShow(keyboarEvent: KeyboardEvent ) {
        let keyboardFrame = keyboarEvent.keyboardFrameEnd
        let keyboardHeight = keyboardFrame.height
        print("Keyboard Height from observer:",keyboardHeight)
    }
    
    
    func handleKeyboardWillChangeFrame(keyboardEvent: KeyboardEvent) {
        
        
        let uiScreenHeight = UIScreen.main.bounds.size.height
        let endFrame = keyboardEvent.keyboardFrameEnd
        
        let endFrameY = endFrame.origin.y
        
        let offset = -1 * endFrame.size.height
        
        if endFrameY >= uiScreenHeight {
            self.discussionsMessageBoxBottomAnchor.constant = 0.0
            self.discussionChatView.discussionTableView.contentOffset.y += 2 * offset
        } else {
            self.discussionsMessageBoxBottomAnchor.constant = offset
            self.discussionChatView.discussionTableView.contentOffset.y -= offset
            print("Shifted visible paths: ",self.discussionChatView.discussionTableView.indexPathsForVisibleRows)
        }
        
        UIView.animate(
            withDuration: keyboardEvent.duration,delay: TimeInterval(0),options: keyboardEvent.options,animations: {
                self.view.layoutIfNeeded()
                
            },completion: nil)
    }
}

//MARK:- Login Handler

extension discussionsViewController: GIDSignInDelegate {
    func sign(_ signIn: GIDSignIn!,didSignInFor user: GIDGoogleUser!,withError error: Error?) {
          // ...
          if let error = error {
              // ...
            print("Error signing in")
            print(error)
              return
          }
                  
          guard let authentication = user.authentication else { return }
          let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken,accesstoken: authentication.accesstoken)
          
          Auth.auth().signIn(with: credential) { (authResult,error) in
              if let error = error {
                  print("authentication error \(error.localizedDescription)")
              }
          }
        setUserProfileImage()
      }
      
      func sign(_ signIn: GIDSignIn!,diddisconnectWith user: GIDGoogleUser!,withError error: Error!) {
          // Perform any operations when the user disconnects from app here.
          // ...
      }
    
    func setUserProfileImage() {
        discussionChatView.saveUserEmail()
        guard let googleUser = GIDSignIn.sharedInstance()?.currentUser else { return }
        guard let userImageUrl = googleUser.profile.imageURL(withDimension: 40) else { return }
        URLSession.shared.dataTask(with: userImageUrl) { (data,response,error) in
            
            guard let data = data,error == nil else { return }
            dispatchQueue.main.async() { [weak self] in
               let userImage = UIImage(data: data)
               self?.userProfileButton.setimage(userImage,for: .normal)
           }
        }.resume()
    }
}

聊天视图代码

class discussionChatView: UIView {
    
    let discussionChatId = "discussionChatID"
    let discussionTableView: UITableView
    var messages: [String: [discussionMessage]]  = [:]
    var messageSendDates: [String] = []
    
    var userEmail = "UserNotLoggedIn"
    var first = true
    
    override init(frame: CGRect) {
        discussionTableView = UITableView()
        
        super.init(frame: frame)
        
        let tapRecognizer = UITapGestureRecognizer(target: self,action: #selector(sendTapNotification))
        discussionTableView.addGestureRecognizer(tapRecognizer)
        
        //        saveUserEmail()
        
        discussionTableView.register(discussionChatMessageCell.self,forCellReuseIdentifier: discussionChatId)
        discussionTableView.delegate = self
        discussionTableView.dataSource = self
        
        discussionTableView.estimatedRowHeight = 30
        discussionTableView.rowHeight = UITableViewAutomaticDimension
        
        self.addSubview(discussionTableView)
        discussionTableView.translatesAutoresizingMaskIntoConstraints = false
        discussionTableView.backgroundColor = .clear
        discussionTableView.allowsSelection = false
        discussionTableView.separatorStyle = .none
        
        NSLayoutConstraint.activate([
            discussionTableView.leadingAnchor.constraint(equalTo: self.leadingAnchor),discussionTableView.trailingAnchor.constraint(equalTo: self.trailingAnchor),discussionTableView.topAnchor.constraint(equalTo: self.topAnchor),discussionTableView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
        ])
        
        loadInitialMessages()
        appendNewMessages()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func loadInitialMessages() {
        messagesReference.queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
            
            guard let value = snapshot.value as? [String: Any] else {return}
            do {
                let data = try JSONSerialization.data(withJSONObject: value,options: .prettyPrinted)
                let messages = try JSONDecoder().decode([String: discussionMessage].self,from: data)
                var messagesList = messages.map { $0.1 }
                messagesList = messagesList.sorted(by: {
                    $0.messageTimestamp < $1.messageTimestamp
                })
                
                for message in messagesList {
                    
                    let dateString = self.getDateString(from: message.messageTimestamp)
                    
                    if !self.messageSendDates.contains(dateString) {
                        self.messageSendDates.append(dateString)
                    }
                    
                    self.messages[dateString,default: [discussionMessage]()].append(message)
                }
                
                self.discussionTableView.reloadData()
            } catch {
                print(error)
            }
        }
    }
    
    func appendNewMessages() {
        messagesReference.queryLimited(toLast: 1).observe(.childAdded) { (snapshot) in
            
            if self.first {
                self.first = false
                return
            }
            
            self.saveUserEmail()
            
            if  let value = snapshot.value {
                do {
                    
                    var lastCellWasVisible: Bool = false
                    if let visiblePaths = self.discussionTableView.indexPathsForVisibleRows {
                        print("Visible paths: ",visiblePaths) 
                        
                        print("Sections: ",self.messageSendDates.count - 1)
                        print("Row: ",self.messages[self.messageSendDates.last ?? "",default: [discussionMessage]()].count - 1)
                        
                        lastCellWasVisible = visiblePaths.contains([self.messageSendDates.count - 1,default: [discussionMessage]()].count - 1])
                    }
                    
                    let data = try JSONSerialization.data(withJSONObject: value,options: .prettyPrinted)
                    let message = try JSONDecoder().decode(discussionMessage.self,from: data)
                    
                    let dateString = self.getDateString(from: message.messageTimestamp)
                    
                    if !self.messageSendDates.contains(dateString) {
                        self.messageSendDates.append(dateString)
                        let indexSet = IndexSet(integer: self.messageSendDates.count - 1)
                        self.discussionTableView.performBatchUpdates({
                            self.discussionTableView.insertSections(indexSet,with: .automatic)
                        }) { (update) in
                            print("Update Success")
                            print("Last cell visible",lastCellWasVisible)
                            self.insertMessage(dateString: dateString,message: message)
                        }
                    } else {
                        print("Last cell visible",lastCellWasVisible)
                        self.insertMessage(dateString: dateString,message: message)
                    }
                    
                    if lastCellWasVisible {
                        self.scrollTableViewToEnd()
                        // This is not working
                        // This is not working
                        // This is not working
                    } else {
                        Toast.show(message: "New Message",type: .Info)
                    }
                } catch {
                    print(error)
                }
            }
        }
    }
    
    func insertMessage(dateString: String,message: discussionMessage) {
        messages[dateString,default: [discussionMessage]()].append(message)
        let indexPath = IndexPath(row:(self.messages[dateString,default: [discussionMessage]()].count - 1),section: self.messageSendDates.index(of: dateString) ?? 0)
        
        self.discussionTableView.insertRows(at: [indexPath],with: .automatic)
    }
}

extension discussionChatView: UITableViewDelegate,UITableViewDataSource {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return messageSendDates.count
    }
    
    func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
        return messages[messageSendDates[section],default: [discussionMessage]()] .count
    }
    
    func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let discussionChatMessageCell = tableView.dequeueReusableCell(withIdentifier: discussionChatId,for: indexPath) as? discussionChatMessageCell else { return UITableViewCell()}
        
        
        let message = messages[messageSendDates[indexPath.section],default: [discussionMessage]()][indexPath.row]
        discussionChatMessageCell.configureCell(message:message,isSender: message.userEmailAddress == userEmail)
        
        return discussionChatMessageCell
    }
    
    func tableView(_ tableView: UITableView,viewForHeaderInSection section: Int) -> UIView? {
        
        let headerLabelView = UILabel(frame: CGRect(x: 0,y: 0,width: discussionTableView.frame.size.width,height: 60))
        let headerLabel = UILabel(frame: CGRect(x: (discussionTableView.frame.size.width-100)/2,y: 20,width: 100,height: 40))
        
        headerLabel.adjustsFontSizetoFitWidth = true
        headerLabel.font = UIFont(name: "Helvetica Neue",size: 13)!
        headerLabel.backgroundColor = UIColor.white
        headerLabel.textAlignment = .center
        headerLabel.textColor = UIColor.black
        
        headerLabelView.addSubview(headerLabel)
        headerLabel.clipsToBounds = true
        headerLabel.layer.cornerRadius = 10
        
        headerLabel.text = getDateStringForHeaderText(dateString: messageSendDates[section])
        
        return headerLabelView
        
    }
    
    func tableView(_ tableView: UITableView,willdisplay cell: UITableViewCell,forRowAt indexPath: IndexPath) {
        cell.backgroundColor = .clear
    }
    
    func tableView(_ tableView: UITableView,heightForHeaderInSection section: Int) -> CGFloat {
        return 60
    }
    
    //    func tableView(_ tableView: UITableView,shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
    //        return true
    //    }
    //
    //    override func canPerformAction(_ action: Selector,withSender sender: Any?) -> Bool {
    //        return true
    //    }
    
}

//MARK:- Utility Functions

extension discussionChatView {
    
    func saveUserEmail() {
        if userEmail != "UserNotLoggedIn" { return }
        if let currentUser = GIDSignIn.sharedInstance().currentUser {
            userEmail = currentUser.profile.email
            print("Email: ",userEmail)
            discussionTableView.reloadData()
            scrollTableViewToEnd(animated: false)
        }
    }
    
    func getDateFormatter() -> DateFormatter {
        let dateFormatter = DateFormatter()
        dateFormatter.timeZone = .current
        dateFormatter.dateFormat = "dd MMM yyyy"
        return dateFormatter
    }
    
    func getDate(from dateString: String) -> Date? {
        //        print("Date String: ",dateString)
        let dateFormatter = getDateFormatter()
        return dateFormatter.date(from: dateString) ?? nil
    }
    
    func getDateString(from timestamp: Double) -> String {
        let dateFormatter = getDateFormatter()
        let date = Date(timeIntervalSince1970: timestamp)
        let dateString = dateFormatter.string(from: date)
        return dateString
    }
    
    func getDateStringForHeaderText(dateString: String) -> String {
        guard let date = getDate(from: dateString) else {
            //            print("Could not get date for generting header string")
            return dateString
        }
        //        print("Date: ",date.description(with: .current))
        if Calendar.current.isDateInToday(date) { return "Today"}
        if Calendar.current.isDateInYesterday(date) {return "Yesterday"}
        return dateString
    }
    
    func scrollTableViewToEnd(animated: Bool = true) {
        
        dispatchQueue.main.asyncAfter(deadline: dispatchTime.Now(),execute: {
            let indexPath = IndexPath(row: self.messages[self.messageSendDates.last ?? "",default: [discussionMessage]()].count - 1,section: self.messageSendDates.count - 1)
            if self.discussionTableView.isValid(indexPath: indexPath) {
                self.discussionTableView.scrollToRow(at: indexPath,at: UITableViewScrollPosition.bottom,animated: animated)
            }
        })
    }
}

//MARK:- Actions

extension discussionChatView {
    @objc func sendTapNotification() {
        NotificationCenter.default.post(name: NSNotification.Name(chatViewTappednotificationName),object: nil)
    }
}

相关问题:Smoothly scrolling tableview up by fixed constant when keyboard appears so last cells are still visible

解决方法

与其调整tableView的偏移量,不如修改contentInset/adjustedContentInset。

您也可以尝试将 automaticallyAdjustsScrollIndicatorInsets 设置为 true,这样您就完全不需要手动更改偏移量了。

您可能仍需要使用 scrollToRow(at:at:animated:) 来保持最新行可见。

编辑:

我整理了一个小示例应用程序,可能会稍微澄清一下。

//
//  ViewController.swift
//  TableViewTest
//
//  Created by Dirk Mika on 21.01.21.
//

import UIKit

class ViewController: UIViewController
{
    let keyboardObserver = KeyboardObserver()
    var tableView: UITableView!
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        tableView = UITableView(frame: CGRect.zero,style: .grouped)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.translatesAutoresizingMaskIntoConstraints = false
        let textField = UITextField(frame: CGRect(x: 0.0,y: 0.0,width: self.view.bounds.size.width,height: 44.0))
        textField.borderStyle = .roundedRect
        tableView.tableFooterView = textField
        view.addSubview(tableView)
        NSLayoutConstraint.activate([
            tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),tableView.topAnchor.constraint(equalTo: self.view.topAnchor),tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
        ])
        tableView.register(UITableViewCell.self,forCellReuseIdentifier: "cell")
        
        keyboardObserver.observe { [weak self] (event) -> Void in
            guard let self = self else { return }
            switch event.type {
            case .willChangeFrame:
                self.handleKeyboardWillChangeFrame(keyboardEvent: event)
            default:
                break
            }
        }
    }
    
    override func viewDidAppear(_ animated: Bool)
    {
        super.viewDidAppear(animated)
        tableView.scrollToRow(at: IndexPath(row: 19,section: 0),at: .bottom,animated: true)
    }
    
    func handleKeyboardWillChangeFrame(keyboardEvent: KeyboardEvent)
    {
        let keyboardFrame = keyboardEvent.keyboardFrameEnd
        
        let keyboardWindowFrame = self.view.window!.convert(keyboardFrame,from: nil)
        let relativeFrame = self.view.convert(keyboardWindowFrame,from: nil)
        var bottomOffset = tableView.frame.origin.y + tableView.frame.size.height - relativeFrame.origin.y - self.view.safeAreaInsets.bottom;
        if (bottomOffset < 0.0)
        {
            bottomOffset = 0.0;
        }
        
        var insets = tableView.contentInset
        insets.bottom = bottomOffset
        tableView.contentInset = insets
        tableView.scrollIndicatorInsets  = insets
    }
}


extension ViewController: UITableViewDelegate,UITableViewDataSource
{
    func numberOfSections(in tableView: UITableView) -> Int
    {
        return 1
    }
    
    func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int
    {
        return 20
    }
    
    func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell",for: indexPath)
        cell.textLabel!.text = "\(indexPath.row)"
        return cell
    }
}

德克