如何解决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)
}
}
解决方法
与其调整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
}
}
德克
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。