#22 實作 Table View 的基本功能 -FB 頁面 part3
ViewController+TableViewController
貼文內容Container View連結TableViewController
CommentViewController貼文頂部及留言功能
CommentTableViewController貼文內容及留言內容
CommentViewController
接收上一頁的資料
selectSection選擇的貼文以及shouldOpenKeyboard點選貼文進入內文不會彈出鍵盤,點選留言按鈕進入內文會彈出鍵盤
class CommentViewController: UIViewController {
var selectSection: Int
var shouldOpenKeyboard: Bool
init?(coder: NSCoder, selectSection: Int, shouldOpenKeyboard: Bool ) {
self.selectSection = selectSection
self.shouldOpenKeyboard = shouldOpenKeyboard
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
自訂Navigation bar
func setNavigationbar() {
// 設定左邊返回按鈕
let backButton = UIButton(type: .system)
backButton.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
let backSymbolConfig = UIImage.SymbolConfiguration(weight: .heavy)
let backImage = UIImage(systemName: "chevron.backward", withConfiguration: backSymbolConfig)
backButton.setImage(backImage, for: .normal)
backButton.tintColor = UIColor(red: 5/255, green: 5/255, blue: 5/255, alpha: 1)
backButton.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
let backButtonItem = UIBarButtonItem(customView: backButton)
// 設定左邊ImageView
let profilePictureImageView = UIImageView()
profilePictureImageView.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
profilePictureImageView.image = UIImage(named: postArray[selectSection].profilePictureName)
profilePictureImageView.contentMode = .scaleAspectFill
profilePictureImageView.layer.cornerRadius = 22
profilePictureImageView.clipsToBounds = true
let profilePictureItem = UIBarButtonItem(customView: profilePictureImageView)
// 貼文者資料view
let container = UIView()
// 發文者名稱
let nameLabel = UILabel()
nameLabel.text = postArray[selectSection].userName
nameLabel.font = UIFont.systemFont(ofSize: 17)
nameLabel.textColor = UIColor(red: 5/255, green: 5/255, blue: 5/255, alpha: 1)
nameLabel.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(nameLabel)
// 發文時間
let timeLabel = UILabel()
timeLabel.text = timeAgoDisplay(date: postArray[selectSection].timestamp)
timeLabel.font = UIFont.systemFont(ofSize: 17)
timeLabel.textColor = UIColor(red: 101/255, green: 103/255, blue: 106/255, alpha: 1)
timeLabel.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(timeLabel)
// 分隔符號
let separatorLabel = UILabel()
separatorLabel.text = "."
separatorLabel.font = UIFont(name: "蘋方-繁", size: 17)
separatorLabel.textColor = UIColor(red: 101/255, green: 103/255, blue: 106/255, alpha: 1)
separatorLabel.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(separatorLabel)
// 貼文狀態
let stateButton = UIButton()
if postArray[selectSection].isPublic {
stateButton.setImage(UIImage(systemName: "globe.asia.australia.fill"), for: .normal)
} else {
stateButton.setImage(UIImage(systemName: "person.2.fill"), for: .normal)
}
stateButton.tintColor = UIColor(red: 101/255, green: 103/255, blue: 106/255, alpha: 1)
stateButton.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(stateButton)
// 設定auto layout條件
NSLayoutConstraint.activate([
profilePictureImageView.widthAnchor.constraint(equalToConstant: 44),
profilePictureImageView.heightAnchor.constraint(equalToConstant: 44),
nameLabel.heightAnchor.constraint(equalToConstant: 22),
timeLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor),
timeLabel.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor),
timeLabel.heightAnchor.constraint(equalToConstant: 22),
separatorLabel.widthAnchor.constraint(equalToConstant: 17),
separatorLabel.heightAnchor.constraint(equalToConstant: 22),
stateButton.widthAnchor.constraint(equalToConstant: 22),
stateButton.heightAnchor.constraint(equalToConstant: 22),
nameLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
nameLabel.topAnchor.constraint(equalTo: container.topAnchor),
timeLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
timeLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor),
separatorLabel.leadingAnchor.constraint(equalTo: timeLabel.trailingAnchor),
separatorLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor),
stateButton.leadingAnchor.constraint(equalTo: separatorLabel.trailingAnchor),
stateButton.bottomAnchor.constraint(equalTo: container.bottomAnchor)
])
let userItem = UIBarButtonItem(customView: container)
navigationItem.leftBarButtonItems = [backButtonItem, profilePictureItem, userItem]
}
// 返回上一頁按鈕的功能
@objc func backButtonTapped () {
navigationController?.popViewController(animated: true)
}
留言按鈕樣式設定
系統原有的圖示向上 旋轉調整成向右
func updateUI() {
sendButton.transform = CGAffineTransform(rotationAngle: .pi/2)
sendButton.tintColor = UIColor(red: 100/255, green: 103/255, blue: 106/255, alpha: 1)
}
留言欄隨著鍵盤變動位置(通知功能)
// ContainerView與留言欄auto layout條件
@IBOutlet weak var postContainerViewBottomConstraint: NSLayoutConstraint!
func registerForKeyboardNotifications() {
// 鍵盤出現
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWasShown(_:)), name: UIResponder.keyboardDidShowNotification, object: nil)
// 鍵盤隱藏
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
@objc func keyboardWasShown(_ notification: NSNotification) {
guard let info = notification.userInfo,
let keyboardFrameValue = info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
let tabHeight = tabBarController?.tabBar.frame.size.height else { return }
let keyboardFrame = keyboardFrameValue.cgRectValue
let keyboardSize = keyboardFrame.size
self.commentView.transform = CGAffineTransform(translationX: 0, y: -(keyboardSize.height - tabHeight))
// 變動ContainerView與留言欄auto layout條件
postContainerViewBottomConstraint.constant = -(commentView.frame.height)
// 留言欄移動的動畫設定
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
@objc func keyboardWillBeHidden(_ notification: NSNotification) {
// 重置ContainerView與留言欄auto layout條件
postContainerViewBottomConstraint.constant = 0
commentView.transform = CGAffineTransform(translationX: 0, y: 0)
}
留言功能
@IBAction func commentChangeButtonColor(_ sender: Any) {
if let text = commentTextField.text, !text.isEmpty {
// 輸入文字發送按鈕變藍色
sendButton.tintColor = UIColor(red: 23/255, green: 119/255, blue: 241/255, alpha: 1)
} else {
sendButton.tintColor = UIColor(red: 100/255, green: 103/255, blue: 106/255, alpha: 1)
}
}
@IBAction func comment(_ sender: Any) {
if let text = commentTextField.text, !text.isEmpty {
let postComment = PostComment(userName: "Lou", profilePictureName: "userphoto", content: text, timestamp: Date()) // 留言功能
postArray[selectSection].postComments.append(postComment)
delegate?.updateTable() // 代理CommentTableViewController更新表格
commentTextField.text = "" // 留言發送後 清除文字
view.endEditing(true) // 留言發送後 收鍵盤
sendButton.tintColor = UIColor(red: 100/255, green: 103/255, blue: 106/255, alpha: 1) // 發送鈕顏色變回預設
}
}
// 點選其他地方 收鍵盤
@IBAction func closeKeyboard(_ sender: Any) {
view.endEditing(true)
}
CommentTableViewController委託CommentViewController代理更新表單
ViewController需要用到TableViewController的更新表單功能
且傳遞資料到TableViewController
import UIKit
// 建立更新表格的協議
protocol CommentViewControllerDelegate: AnyObject {
func updateTable()
}
class CommentViewController: UIViewController {
weak var delegate: CommentViewControllerDelegate? // 代理人
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "embedCommentTableViewControllerSegue",
let commentTableVC = segue.destination as? CommentTableViewController {
// 傳遞資料
commentTableVC.selectSection = selectSection
// CommentTableViewController設定為代理人
commentTableVC.delegate = self
// CommentViewController設定為CommentTableViewController代理人
self.delegate = commentTableVC
}
}
}
extension CommentViewController: CommentButtonDelegate {
func commentButtonTapped() {
commentTextField.becomeFirstResponder()
}
}
CommentViewController委託CommentTableViewController代理鍵盤彈出
TableViewController需要用到ViewController留言欄的鍵盤彈出功能
import UIKit
protocol CommentButtonDelegate: AnyObject {
func commentButtonTapped()
}
class CommentTableViewController: UITableViewController {
weak var delegate: CommentButtonDelegate?
@IBAction func commentButtonTapped(_ sender: UIButton) {
delegate?.commentButtonTapped()
}
}
extension CommentTableViewController: CommentViewControllerDelegate {
func updateTable() {
tableView.reloadData()
}
}
CommentTableViewController
override func numberOfSections(in tableView: UITableView) -> Int {
guard let selectSection = selectSection else { return 3 }
return 3 + postArray[selectSection].postComments.count // 留言數增加Section
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
guard let selectSection = selectSection else { return UITableViewCell() }
if indexPath.section == 0 {
guard let postCell = tableView.dequeueReusableCell(withIdentifier: "\(PostTableViewCell.self)", for: indexPath) as? PostTableViewCell else { fatalError("PostTableViewCell failed") }
let postInfo = postArray[selectSection]
postCell.updateUI(with: postInfo)
cell = postCell
} else if indexPath.section == 1 {
guard let postButtonCell = tableView.dequeueReusableCell(withIdentifier: "\(PostButtonTableViewCell.self)", for: indexPath) as? PostButtonTableViewCell else { fatalError("PostBarTableViewCell failed") }
let postInfo = postArray[selectSection]
postButtonCell.updateUI(with: postInfo)
cell = postButtonCell
} else if indexPath.section == 2 {
guard let likesCell = tableView.dequeueReusableCell(withIdentifier: "\(LikesTableViewCell.self)", for: indexPath) as? LikesTableViewCell else { fatalError("LikesTableViewCell failed") }
let postInfo = postArray[selectSection]
likesCell.updateUI(with: postInfo)
cell = likesCell
} else {
guard let commentCell = tableView.dequeueReusableCell(withIdentifier: "\(CommentTableViewCell.self)" , for: indexPath) as? CommentTableViewCell else { fatalError("CommentTableViewCell failed ") }
let postComment = postArray[selectSection].postComments[indexPath.section - 3]
commentCell.updateUI(with: postComment)
cell = commentCell
}
// 設定背景色為透明
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor.clear
// cell 的 selectedBackgroundView
cell.selectedBackgroundView = backgroundView
return cell
}
四種自訂的TableViewCell
PostTableViewCell (Section 0) 顯示貼文文字內容與圖片
import UIKit
class PostTableViewCell: UITableViewCell {
@IBOutlet weak var postMessageLabel: UILabel!
@IBOutlet weak var postImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func updateUI(with postInfo: PostInfo) {
postMessageLabel.text = postInfo.text
if let imageName = postInfo.imageName {
if UUID(uuidString: imageName) != nil { // imageName是UUID,從文件系統讀取
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentsDirectory.appendingPathComponent(imageName)
postImageView.image = UIImage(contentsOfFile: fileURL.path)
} else { // 從Asset圖庫讀取
postImageView.image = UIImage(named: imageName)
}
} else {
postImageView.image = nil
}
}
}
PostButtonTableViewCell (Section 1) 按讚 留言 分享功能欄
import UIKit
class PostButtonTableViewCell: UITableViewCell {
@IBOutlet weak var likeButton: UIButton!
@IBOutlet weak var commentButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func updateUI(with postInfo: PostInfo){
if postInfo.isLiked {
likeButton.setImage(UIImage(systemName: postInfo.likeButtonImageName), for: .normal)
likeButton.tintColor = UIColor(red: 23/255, green: 119/255, blue: 241/255, alpha: 1)
} else {
likeButton.setImage(UIImage(systemName: postInfo.likeButtonImageName), for: .normal)
likeButton.tintColor = UIColor(red: 100/255, green: 103/255, blue: 106/255, alpha: 1)
}
}
}
LikesTableViewCell (Section 2) 按讚數量 分享數量
import UIKit
class LikesTableViewCell: UITableViewCell {
@IBOutlet weak var LikesLabel: UILabel!
@IBOutlet weak var sharesLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func updateUI(with postInfo: PostInfo){
LikesLabel.text = String(postInfo.likes)
sharesLabel.text = String(postInfo.shares)+"次分享"
}
}
CommentTableViewCell (Section 3) 留言內容
import UIKit
class CommentTableViewCell: UITableViewCell {
@IBOutlet weak var userImageView: UIImageView!
@IBOutlet weak var commentLabel: UILabel!
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var likeButton: UIButton!
@IBOutlet weak var likesLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func updateUI(with postComment: PostComment) {
userImageView.image = UIImage(named: postComment.profilePictureName)
commentLabel.text = "\(postComment.userName)\n\(postComment.content)"
timeLabel.text = timeAgoDisplay(date: postComment.timestamp)
if postComment.likes != 0 {
likesLabel.text = "\(postComment.likes)👍"
} else {
likesLabel.text = ""
}
if postComment.isLiked {
likeButton.tintColor = UIColor(red: 23/255, green: 119/255, blue: 241/255, alpha: 1)
} else {
likeButton.tintColor = UIColor(red: 100/255, green: 103/255, blue: 106/255, alpha: 1)
}
}
}
貼文按讚功能
@IBAction func likePost(_ sender: UIButton) {
guard let selectSection = selectSection else { return }
postArray[selectSection].isLiked = !postArray[selectSection].isLiked
let likeBtnImageName = postArray[selectSection].likeButtonImageName
sender.setImage(UIImage(systemName: likeBtnImageName), for: .normal)
if postArray[selectSection].isLiked {
sender.tintColor = UIColor(red: 23/255, green: 119/255, blue: 241/255, alpha: 1)
} else {
sender.tintColor = UIColor(red: 100/255, green: 103/255, blue: 106/255, alpha: 1)
}
// 取得likecountLabel所在的 indexPath
let postIndexPath = [IndexPath(row: 0, section: 2)]
// 重新載入表格
tableView.reloadRows(at: postIndexPath, with: .none)
}
留言按讚功能
@IBAction func likeComment(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: tableView)
if let indexPath = tableView.indexPathForRow(at: point),
let selectSection = selectSection {
let section = indexPath.section
let commentSection = section - 3
postArray[selectSection].postComments[commentSection].isLiked = !postArray[selectSection].postComments[commentSection].isLiked
if postArray[selectSection].postComments[commentSection].isLiked {
sender.tintColor = UIColor(red: 23/255, green: 119/255, blue: 241/255, alpha: 1)
} else {
sender.tintColor = UIColor(red: 100/255, green: 103/255, blue: 106/255, alpha: 1)
}
let commentIndexPath = [IndexPath(row: 0, section: section)]
tableView.reloadRows(at: commentIndexPath, with: .none)
}
}
下篇繼續介紹part4