繁体   English   中英

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

[英]UITableView.indexPathsForVisibleRows not returning correct values when table view content is offset to avoid visible keyboard

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

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

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

控制台日志:

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

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

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

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

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

Visible paths:  [[2, 3], [2, 4], [2, 5], [2, 6], [2, 7], [2, 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, constant: 4),
            headerContainer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -4),
        ])
    }
    
    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, constant: 10),
            discussionChatView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
            discussionChatView.topAnchor.constraint(equalTo: countryCountView.bottomAnchor  , constant: 10),
            discussionChatView.bottomAnchor.constraint(equalTo: discussionsMessageBox.topAnchor, constant: -10),
        ])
    }
    
    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, animated: true, completion: nil)
        } else {
            let postSignInAlert = UIAlertController(title: "Discussions", message: postLoginInfoMessage, preferredStyle: .alert)
            let dismissAction = UIAlertAction(title: "Okay", style: .cancel) { _ in }
            postSignInAlert.addAction(dismissAction)
            present(postSignInAlert, animated: true, 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, animated: true, 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, self.messages[self.messageSendDates.last ?? "", 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)
    }
}

相关问题: 当键盘出现时,通过固定常量平滑滚动表格视图,因此最后一个单元格仍然可见

与其调整 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
    }
}

短剑

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM