#22 實作 Table View 的基本功能 -FB 頁面 part1

Lou
彼得潘的 Swift iOS / Flutter App 開發教室
18 min readJul 17, 2023

有87分像的FB介面

完美仿照

貼文資料

手動編輯貼文內容
struct PostInfo { // 貼文資訊
let userName: String
let profilePictureName: String
let timestamp: Date
let isPublic: Bool
let text: String?
let imageName: String?
var likes: Int = 0
var commentsCount: Int { // 計算屬性(Computed Properties)
var count = postComments.count
for postComment in postComments {
count += postComment.replyComments.count
}
return count
}
var shares: Int = 0
var isLiked: Bool = false { // 觀察屬性(Property Observers)
didSet {
if isLiked {
likes += 1
} else {
likes -= 1
}
}
}
var likeButtonImageName:String { // 計算屬性(Computed Properties)
get {
isLiked ? "hand.thumbsup.fill" : "hand.thumbsup"
}
set {
isLiked = newValue == "hand.thumbsup.fill" ? true : false
}
}
var postComments: [PostComment] = []
}

struct PostComment { // 貼文留言
let userName: String
let profilePictureName: String
let content: String
let timestamp: Date
var isLiked: Bool = false { // 觀察屬性(Property Observers)
didSet {
if isLiked {
likes += 1
} else {
likes -= 1
}
}
}
var likes: Int = 0
var replyComments: [ReplyComments] = []
}

struct ReplyComments { // 貼文留言中的回覆留言
let userName: String
let profilePictureName: String
let content: String
let timestamp: Date
var isLiked: Bool = false { // 觀察屬性(Property Observers)
didSet {
if isLiked {
likes += 1
} else {
likes -= 1
}
}
}
var likes: Int = 0
}

首頁HomePage Table View Controller 佈局

表格分區
設定Content:Dynamic Prototypes、Prototype Cells:4
四種自定義cell Table View Cell填入Identifier
    override func numberOfSections(in tableView: UITableView) -> Int {
return 2 + postArray.count //section 2 開始為貼文內容
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section < 2 ? 1 : 2 //section 2 開始有兩個row
}


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
// 四種cell自訂樣式
if indexPath.section == 0 {
guard let topCell = tableView.dequeueReusableCell(withIdentifier: "\(TopTableViewCell.self)", for: indexPath) as? TopTableViewCell else {
fatalError("TopTableViewCell failed") }
cell = topCell

} else if indexPath.section == 1 {
guard let storyCell = tableView.dequeueReusableCell(withIdentifier: "\(StoryTableViewCell.self)", for: indexPath) as? StoryTableViewCell else { fatalError("StoryTableViewCell failed") }
cell = storyCell

} else if indexPath.row == 0 {
guard let postCell = tableView.dequeueReusableCell(withIdentifier: "\(HomePagePostTableViewCell.self)", for: indexPath) as? HomePagePostTableViewCell else { fatalError("HomePagePostTableViewCell failed") }
let postInfo = postArray[indexPath.section - 2]
postCell.updateUI(with: postInfo)
cell = postCell
} else {
guard let postButtonCell = tableView.dequeueReusableCell(withIdentifier: "\(PostButtonTableViewCell.self)", for: indexPath) as? PostButtonTableViewCell else { fatalError("PostBarTableViewCell failed") }
let postInfo = postArray[indexPath.section - 2]
postButtonCell.updateUI(with: postInfo)
postButtonCell.commentButton.tag = indexPath.section
cell = postButtonCell
}

// 設定背景色為透明
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor.clear

// cell 的 selectedBackgroundView
cell.selectedBackgroundView = backgroundView

return cell
}

StoryTableViewCell (Section 1) 限時動態欄位

自訂Table View Cell

不同Cell內的元件outlet要拉到自訂的class TableViewCell

(action才可以直接拉進HomePageTableViewController)

限時動態欄位Story Table View Cell+Collection View呈現

Table View+Collection View

遵從UICollectionViewDataSource, UICollectionViewDelegate

import UIKit

extension HomePageTableViewController: UICollectionViewDataSource, UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return postArray.count + 1
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

if indexPath.row == 0{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(MyStoryCollectionViewCell.self)", for: indexPath) as? MyStoryCollectionViewCell else { fatalError("MyStoryCollectionViewCell failed") }
cell.updateUI()
return cell
} else {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "\(StoryCollectionViewCell.self)", for: indexPath) as? StoryCollectionViewCell else { fatalError("StoryCollectionViewCell failed") }
let postInfo = postArray[indexPath.row - 1]
cell.updateUI(with: postInfo)
return cell
}
}
}
自訂兩種Collection View Cell

MyStoryCollectionViewCell (Row 0) 限時動態新增欄位

class MyStoryCollectionViewCell: UICollectionViewCell {

@IBOutlet weak var plusButton: UIButton!

func updateUI() {
layer.cornerRadius = 10
layer.borderWidth = 1
layer.borderColor = CGColor(red: 222/255, green: 223/255, blue: 225/255, alpha: 1)
plusButton.layer.cornerRadius = 15
plusButton.layer.borderWidth = 2
plusButton.layer.borderColor = CGColor(red: 1, green: 1, blue: 1, alpha: 1)
}
}

StoryCollectionViewCell (Row 1以後) 限時動態貼文

class StoryCollectionViewCell: UICollectionViewCell {

@IBOutlet weak var storyProfilePictureImageView: UIImageView!
@IBOutlet weak var storyPictureImageView: UIImageView!
@IBOutlet weak var storyNameLabel: UILabel!
@IBOutlet weak var storyBorderIamgeView: UIImageView!

func updateUI(with postInfo: PostInfo) {
layer.cornerRadius = 10
layer.borderWidth = 1
layer.borderColor = CGColor(red: 222/255, green: 223/255, blue: 225/255, alpha: 1)
storyProfilePictureImageView.layer.cornerRadius = 18
storyBorderIamgeView.layer.cornerRadius = 22
storyBorderIamgeView.layer.borderWidth = 2
storyBorderIamgeView.layer.borderColor = CGColor(red: 26/255, green: 116/255, blue: 229/255, alpha: 1)

storyNameLabel.text = postInfo.userName
storyProfilePictureImageView.image = UIImage(named: postInfo.profilePictureName)

if let imageName = postInfo.imageName {
storyPictureImageView.image = UIImage(named: imageName)
} else {
storyPictureImageView.image = nil
}
}
}

HomePagePost與PostButton這兩個TableViewCell有設定更新UI function

HomePagePost與PostButton有自訂更新UI function

HomePagePostTableViewCell (Section 2) 貼文內容

class HomePagePostTableViewCell: UITableViewCell {

@IBOutlet weak var profilePictureImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var stateButton: UIButton!
@IBOutlet weak var messageLabel: UILabel!
@IBOutlet weak var postImageView: UIImageView!
@IBOutlet weak var likeCountLabel: UILabel!
@IBOutlet weak var messageShareLabel: UILabel!

override func awakeFromNib() {
super.awakeFromNib()
}

override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}

func updateUI(with postInfo: PostInfo) {

profilePictureImageView.image = UIImage(named: postInfo.profilePictureName)
nameLabel.text = postInfo.userName
timeLabel.text = timeAgoDisplay(date: postInfo.timestamp)
if postInfo.isPublic {
stateButton.setImage(UIImage(systemName: "globe.asia.australia.fill"), for: .normal)
} else {
stateButton.setImage(UIImage(systemName: "person.2.fill"), for: .normal)
}
messageLabel.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
}

likeCountLabel.text = String(postInfo.likes)
messageShareLabel.text = String(postInfo.commentsCount)+"則留言 "+String(postInfo.shares)+"次分享"
}
}

PostButtonTableViewCell (Section 3) 按讚 留言 分享功能欄

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)
}
}
}

設定表格高度

除了StoryTableViewCell以外的三個cell 都是用auto layout計算高度

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.section == 1 ? 180 : UITableView.automaticDimension
}

其他頁面及功能接續下篇介紹

--

--