输入新值时复制表的最后一行
Posted
技术标签:
【中文标题】输入新值时复制表的最后一行【英文标题】:Duplicating last row of table when new value entered 【发布时间】:2019-04-27 19:19:29 【问题描述】:我的应用中有聊天功能。该页面将现有数据加载为名为 hhmessages 的数组。对话中的最后一条消息是“欢迎”,当我输入新文本“谢谢”并按 Enter 时,表格会自动再次显示“欢迎”而不是“谢谢”。如果它退出页面并返回,它现在会显示“谢谢”作为最后一条消息。它在后端工作,只是即时更新在输入时没有显示 UITableView 中的值。
这是针对 iPhone 应用程序的。 已更新以显示完整代码 - 现在反向已被删除,新条目显示为空白。
import UIKit
import Foundation
extension String
// Calculeta the hight string Function
func calculateTextFrameRect(
objectsInPlaceHeight: CGFloat,
objectsInPlaceWidth: CGFloat,
fontSize: CGFloat,
fontWeight: CGFloat) -> CGSize
let bounding = CGSize(width: UIScreen.main.bounds.width - objectsInPlaceWidth, height: .infinity)
let rect = NSString(string: self).boundingRect(
with: bounding,
options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin),
attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: fontSize, weight: UIFont.Weight(rawValue: fontWeight))],
context: nil)
return CGSize(width: UIScreen.main.bounds.width, height: rect.height + objectsInPlaceHeight )
// Messages for test
let frame = CGRect(origin: .zero, size: CGSize.init(width: 375, height: 559))
class Message
let message: String
var incoming: [Int]
let image: UIImage
var avas = UIImage()
init(message: String, image: UIImage, incoming: Int, avas: UIImage)
self.message = message
self.image = image
self.incoming = [incoming]
self.avas = avas
class ConversationViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITableViewDelegate, UITableViewDataSource, UITextViewDelegate
//var user = NSDictionary()
var messages = NSDictionary()
//var guest = NSDictionary()
@IBOutlet var senderLbl: UILabel!
@IBOutlet var senderageLbl: UILabel!
@IBOutlet var senderraceLbl: UILabel!
@IBOutlet var sendergenderLbl: UILabel!
@IBOutlet var sendercityLbl: UILabel!
@IBOutlet var senderstateLbl: UILabel!
// @IBOutlet var tableView: UITableView!
var hhmessages = [AnyObject]()
//var messages: [Message] = []
var pictures = [UIImage]()
var avas = [UIImage]()
var avaURL = [String]()
var isLoading = false
var skip = 0
var limit = 50
var images = [UIImage]()
var incoming: [Int] = []
var comments = [String]()
var ids = [String]()
//var isSent: String = ""
var isPictureSelected = false
//var currentUser_ava = [Any]()
@IBOutlet var pictureImg: UIImageView!
@IBOutlet var avaImg: UIImageView!
@IBOutlet var viewprofile_btn: UIButton!
@IBOutlet var imgprofile_btn: UIButton!
@IBOutlet var replyTxt: UITextView!
//var replyTxt:UITextView!
@IBOutlet var replyTxt_height: NSLayoutConstraint!
@IBOutlet var replyTxt_bottom: NSLayoutConstraint!
@IBOutlet var picSelect: UIButton!
@IBOutlet var replyBtn: UIButton!
var puuid = String()
var imageSelected = false
var coolIndicator: UIActivityIndicatorView!
var commentsTextView_bottom_identity = CGFloat()
@IBOutlet var tableView: UITableView!
// Table View here + basic configuration
override func viewDidLoad()
//self.tabBarController?.tabBar.isHidden = true
super.viewDidLoad()
tableView.transform = CGAffineTransform(rotationAngle: -(CGFloat)(Double.pi));
// dynamic cell height
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
loadPosts()
replyTxt.layer.cornerRadius = replyTxt.bounds.width / 50
replyTxt.backgroundColor = UIColor.clear
replyTxt.layer.borderColor = UIColor.gray.cgColor
replyTxt.layer.borderWidth = 1.0
let username = messages["sender"] as? String
self.navigationItem.title = username
// pre last func
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(animated)
// remove observers of notification when the viewController is left
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
// exec-d once notification is caught -> KeyboardWillShow
@objc func keyboardWillShow(_ notification: Notification)
// getting the size of the keyboard
if let keyboard_size = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
// increasing the bottom constraint by the keyboard's height
replyTxt_bottom.constant += keyboard_size.height
// updating the layout with animation
UIView.animate(withDuration: 0.3)
self.view.layoutIfNeeded()
// exec-d once notification is caught -> KeyboardWillHide
@objc func keyboardWillHide()
// updating the layout with animation
UIView.animate(withDuration: 0.3)
self.view.layoutIfNeeded()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
//replyTxt().resignFirstResponder()
self.view.endEditing(false)
// TABLEVIEW
// Number os cells
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return hhmessages.count
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let colorSmoothGray = UIColor(red: 229/255, green: 229/255, blue: 234/255, alpha: 1)
let colorBrandBlue = UIColor(red: 148 / 255, green: 33 / 255, blue: 147 / 255, alpha: 1)
let pictureURL = hhmessages[indexPath.row]["uploadpath"] as? String
print("test 1", pictureURL)
// no picture in the post
if pictureURL == nil || pictureURL == ""
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ConversationCell
cell.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
// shortcuts
let hhpost = hhmessages[indexPath.row]
let text = hhpost["messagetext"] as? String
cell.messageLbl.text = text
cell.messageLbl.textAlignment = .right
cell.messageLbl.backgroundColor = colorSmoothGray
cell.messageLbl.textColor = .black
cell.messageLbl.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
cell.messageLbl.font?.withSize(25)
cell.messageLbl.clipsToBounds = true
cell.messageLbl.sizeToFit()
pictures.append(UIImage())
return cell
else
let cell = tableView.dequeueReusableCell(withIdentifier: "PicCell", for: indexPath) as! PicConversationCell
cell.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
let smimages = hhmessages[indexPath.row]["path"] as? UIImage
cell.smavaImg.image = smimages
for i in 0 ..< self.incoming.count
// Confiture the constraints for cell
if self.incoming[indexPath.row] == 1
// Text
cell.messageLbl.textAlignment = .left
cell.messageLbl.backgroundColor = colorBrandBlue
cell.messageLbl.textColor = .white
cell.messageLbl.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
cell.messageLbl.font?.withSize(25)
cell.messageLbl.clipsToBounds = true
// Constraints
cell.lefBubbleConstraint.isActive = true
cell.rightBubbleConstraint.isActive = false
if cell.postpictureImg.image == nil
cell.postpictureImg.backgroundColor = .black
cell.postpictureImg.clipsToBounds = true
else
cell.postpictureImg.backgroundColor = .black
cell.postpictureImg.clipsToBounds = true
else if self.incoming[indexPath.row] == 0
// Text
cell.messageLbl.textAlignment = .right
cell.messageLbl.backgroundColor = colorSmoothGray
cell.messageLbl.textColor = .black
cell.messageLbl.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
cell.messageLbl.font?.withSize(25)
cell.messageLbl.clipsToBounds = true
// Constraints
cell.lefBubbleConstraint.isActive = false
cell.rightBubbleConstraint.isActive = true
if cell.postpictureImg.image == nil
cell.postpictureImg.backgroundColor = .black
cell.postpictureImg.clipsToBounds = true
else
cell.postpictureImg.backgroundColor = .black
cell.postpictureImg.clipsToBounds = true
if cell.lefBubbleConstraint.isActive == true
cell.lefImageConstraint.isActive = true
cell.rightImageConstraint.isActive = false
else
cell.lefImageConstraint.isActive = false
cell.rightImageConstraint.isActive = true
let pictureString = hhmessages[indexPath.row]["uploadpath"] as! String
let pictureURL = URL(string: pictureString)!
// if there are still pictures to be loaded
if hhmessages.count != pictures.count
URLSession(configuration: .default).dataTask(with: pictureURL) (data, response, error) in
// downloaded
if let image = UIImage(data: data!)
self.pictures.append(image)
DispatchQueue.main.async
cell.postpictureImg.image = image
.resume()
// cached picture
else
DispatchQueue.main.async
cell.postpictureImg.image = self.pictures[indexPath.row]
return cell
// pre load func
override func viewDidAppear(_ animated: Bool)
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
// func of loading posts from server
@objc func loadPosts()
//isLoading = true
let me = user!["username"] as! String
let meid = user!["id"] as! String
print(meid)
print(me)
//print(username)
let uuid = messages["uuid"] as! String
print(uuid)
// accessing php file via url path
let url = URL(string: "http://localhost/message.php")!
// pass information to php file
let body = "username=\(me)&uuid=\(uuid)&recipient_id=\(meid)"
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body.data(using: String.Encoding.utf8)
tableView.transform = CGAffineTransform(rotationAngle: -(CGFloat)(Double.pi));
// launch session
URLSession.shared.dataTask(with: request) (data, response, error) in
DispatchQueue.main.async
// no error of accessing php file
// error occured
if error != nil
Helper().showAlert(title: "Server Error", message: error!.localizedDescription, in: self)
//self.isLoading = false
return
do
// access data - safe mode
guard let data = data else
Helper().showAlert(title: "Data Error", message: error!.localizedDescription, in: self)
//self.isLoading = false
return
// getting content of $returnArray variable of php file
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
// accessing json data - safe mode
guard let posts = json?["messages"] as? [NSDictionary] else
//self.isLoading = false
return
// assigning all successfully loaded posts to our Class Var - posts (after it got loaded successfully)
self.hhmessages = posts
self.tableView.reloadData()
// scroll to the latest index (latest cell -> bottom)
let indexPath = IndexPath(row: self.hhmessages.count - 1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
// self.isLoading = false
catch
Helper().showAlert(title: "JSON Error", message: error.localizedDescription, in: self)
//self.isLoading = false
return
.resume()
@IBAction func viewprofile_clicked(_ sender: Any)
// performSegue(withIdentifier: "guest2", sender: self.guest)
@IBAction func imgprofile_clicked(_ sender: Any)
// performSegue(withIdentifier: "guest2", sender: self.guest)
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
if segue.identifier == "guest"
if let destination = segue.destination as? GuestViewController
destination.guest = messages
// function sending requset to PHP to uplaod a file
func uploadPost()
// validating vars before sending to the server
guard let user_id = user?["id"] as? String, let username = user?["username"] as? String, let avaPath = user?["ava"] else
// converting url string to the valid URL
if let url = URL(string: user?["ava"] as! String)
// downloading all data from the URL
guard let data = try? Data(contentsOf: url) else
return
// converting donwloaded data to the image
guard let image = UIImage(data: data) else
return
// assigning image to the global var
let currentUser_ava = image
return
let user_id_int = Int(user_id)!
let messagetext = replyTxt.text.trimmingCharacters(in: .whitespacesAndNewlines)
hhmessages.insert(messagetext as AnyObject, at: hhmessages.endIndex)
let indexPath = IndexPath(row: hhmessages.count - 1, section: 0)
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
tableView.transform = CGAffineTransform(rotationAngle: -(CGFloat)(Double.pi));
tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
replyTxt.text = ""
textViewDidChange(replyTxt)
let recipient = messages["username"] as! String
let rid = String(describing: messages["recipient_id"]!)
let uuid = messages["uuid"] as! String
puuid = UUID().uuidString
// prepare request
let url = URL(string: "http://localhost/messagepost.php")!
let body = "sender_id=\(user_id)&sender=\(username)&text=\(messagetext)&recipient_id=\(rid)&recipient=\(recipient)&uuid=\(uuid)&puuid=\(puuid)"
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body.data(using: .utf8)
// send request
URLSession.shared.dataTask(with: request) (data, response, error) in
DispatchQueue.main.async
// error happened
if error != nil
Helper().showAlert(title: "Server Error", message: error!.localizedDescription, in: self)
return
do
// converting received data from the server into json format
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary
// safe mode of casting json
guard let parsedJSON = json else
return
// if the status of JSON is 200 - success
if parsedJSON["status"] as! String == "200"
else
Helper().showAlert(title: "400", message: parsedJSON["status"] as! String, in: self)
return
// json error
catch
Helper().showAlert(title: "JSON Error", message: error.localizedDescription, in: self)
return
.resume()
// exec-d whenever delegated textView has been changed by chars
func textViewDidChange(_ textView: UITextView)
// declaring new size of the textView. we increase the height
let new_size = textView.sizeThatFits(CGSize.init(width: textView.frame.width, height: CGFloat(MAXFLOAT)))
// assign new size to the textView
textView.frame.size = CGSize.init(width: CGFloat(fmaxf(Float(new_size.width), Float(textView.frame.width))), height: new_size.height)
//UIView.animate(withDuration: 0.2)
self.view.layoutIfNeeded()
//
@IBAction func picSelect_clicked(_ sender: AnyObject)
// calling picker for selecting iamge
showActionSheet()
// this function launches Action Sheet for the photos
func showActionSheet()
// declaring action sheet
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// declaring camera button
let camera = UIAlertAction(title: "Camera", style: .default) (action) in
// if camera available on device, than show
if UIImagePickerController.isSourceTypeAvailable(.camera)
self.showPicker(with: .camera)
// declaring library button
let library = UIAlertAction(title: "Photo Library", style: .default) (action) in
// checking availability of photo library
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary)
self.showPicker(with: .photoLibrary)
// declaring cancel button
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
// adding buttons to the sheet
sheet.addAction(camera)
sheet.addAction(library)
sheet.addAction(cancel)
// present action sheet to the user finally
self.present(sheet, animated: true, completion: nil)
// takes us to the PickerController (Controller that allows us to select picture)
func showPicker(with source: UIImagePickerControllerSourceType)
let picker = UIImagePickerController()
picker.delegate = self
picker.allowsEditing = true
picker.sourceType = source
present(picker, animated: true, completion: nil)
// executed whenever the image has been picked via pickerController
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
// accessing selected image
let image = info[UIImagePickerControllerEditedImage] as? UIImage
// assigning selected image to pictureImageView
pictureImg.image = image
// cast boolean as TRUE -> Picture Is Selected
isPictureSelected = true
// remove pickerController
pictureImg.image = info[UIImagePickerControllerEditedImage] as? UIImage
self.dismiss(animated: true, completion: nil)
// cast as a true to save image file in server
if pictureImg.image == info[UIImagePickerControllerEditedImage] as? UIImage
imageSelected = true
dismiss(animated: true, completion: nil)
// exec when pictureImageView has been tapped
@IBAction func pictureImageView_tapped(_ sender: Any)
// declaring action sheet
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// declaring delete button
let delete = UIAlertAction(title: "Delete", style: .destructive) (action) in
self.pictureImg.image = UIImage()
// declaring cancel button
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
// adding buttons to the sheet
sheet.addAction(delete)
sheet.addAction(cancel)
// present action sheet to the user finally
self.present(sheet, animated: true, completion: nil)
// custom body of HTTP request to upload image file
func createBodyWithParams(_ parameters: [String: String]?, filePathKey: String?, imageDataKey: Data, boundary: String) -> Data
let body = NSMutableData();
if parameters != nil
for (key, value) in parameters!
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
// if file is not selected, it will not upload a file to server, because we did not declare a name file
var filename = ""
if imageSelected == true
filename = "notes-\(puuid).jpg"
let mimetype = "image/jpg"
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.append(imageDataKey)
body.appendString("\r\n")
body.appendString("--\(boundary)--\r\n")
return body as Data
@IBAction func replyBtn_clicked(_ sender: Any)
if replyTxt.text.isEmpty == false && replyTxt.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
uploadPost()
//tableView.reloadData()
@objc func keyboardWillChange(notification: NSNotification)
let duration = notification.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIKeyboardAnimationCurveUserInfoKey] as! UInt
let curFrame = (notification.userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let targetFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = targetFrame.origin.y - curFrame.origin.y
//print("deltaY",deltaY)
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations:
self.replyTxt.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
self.tableView.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
self.replyBtn.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
self.picSelect.frame.origin.y+=deltaY // Here You Can Change UIView To UITextField
,completion: nil)
func textFieldShouldReturn(_ replyTxt: UITextField) -> Bool
replyTxt.resignFirstResponder()
return true
【问题讨论】:
numberOfRows 函数的代码是什么? @MaryamFekri 刚刚更新为显示 numberOfRows: func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int return hhmessages.count 我认为,如果您完全滚动表格,tableView 再次为所有行调用 cellForRow,它将修复您的行。但可以肯定这不是解决方案,我只想知道这是否是我正在考虑的状态 我建议先评论这一行:tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) 我觉得这个会再次调用你的 cellForRow 函数,因为你有将文本添加到您的数组中,然后它将再次显示为最后一行 你也可以在 cellForRow 函数中下一个断点,并检查在哪一行代码之后它被调用了多少次 【参考方案1】:问题是您在
处插入新文本let indexPath = IndexPath(row: hhmessages.count - 1, section: 0)
这是数组的最后一个字段。 但在你的 cellForRow 中:你有
let text = hhmessages.reversed()[indexPath.row]["messagetext"] as! String
它在 tableView 的最后一个单元格中显示数组的第一个字段。
我认为那是你的问题。
【讨论】:
感谢@Maryam,不幸的是,我尝试了您推荐的不同变体,但没有运气。我会按要求发布我的整个视图控制器。 嗨@Maryam,我删除了反向并更改了其他一些代码,但现在它显示为空白而不是重复最后一行。 在看到您的代码之前,我无法告诉您出了什么问题。您应该跟踪您的代码并查看每一步需要多少行以及每一步的array.count 是多少,如果一切都匹配,那么您需要检查您的字符串数组,看看它是否符合您想要的顺序。【参考方案2】:就像其他人所说的那样,您在 hhmessages 中弄乱了您的订单。
假设您有消息Welcome
、Hello
,并且您正在尝试添加Thank Your
,您的代码包含更多 cmets:
self.hhmessages.insert(messagetext as AnyObject, at: hhmessages.endIndex) // Thank You is inserted to the end of the array at 3rd position (2)
let indexPath = IndexPath(row: hhmessages.count - 1, section: 0) // ok, let's get index of 3rd row (2)
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .automatic) // add new row to the end
tableView.endUpdates()
然后 UIKit 要求您在位置 2 加载这个新的第 3 行,然后您调用:
let text = hhmessages.reversed()[indexPath.row]["messagetext"] as! String
问题是hhmessages.reversed()
反转了您的数组,实际上使Thank you
在位置0 的第一个,Welcome
在位置2 的最后一个,这将得到文本Welcome
。
我看到的第二个问题是这一行:
let pictureURL = hhmessages[indexPath.row]["uploadpath"] as? String
在这里你得到了未经审查的图片网址,这至少很奇怪。
【讨论】:
感谢@KleMix,我尝试删除反向,但没有任何反应。我在那里有反转,所以我的桌子将从底部加载 我删除了反向并更改了一些其他代码,但现在它显示为空白而不是重复最后一行以上是关于输入新值时复制表的最后一行的主要内容,如果未能解决你的问题,请参考以下文章
Hudi 0.5.2 Hudi 写时复制 读时合并表区别联系