Preview

Topics

1. Collection View

設置UICollectionView(集合視圖)通常涉及幾個步驟,以下是一個基本的設置方法:

  1. 創建UICollectionView:視圖控制器中,創建一個UICollectionView實例。
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.delegate = self
collectionView.dataSource = self

2. 設置佈局:UICollectionView需要一個布局對象來確定單元格的排列方式。可以使用預設的流式布局,也可以自定義布局。

class YourViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
collectionView.collectionViewLayout = layout
}

3. 設置每個 Cell 的尺寸大小

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let numberOfColumns: CGFloat = 3 // 每一行的cell數目
let cellWidth = (collectionView.bounds.width - (numberOfColumns - 1) * itemSpace) / numberOfColumns
// 計算每個cell的寬度,考慮到cell之間的間距

return CGSize(width: cellWidth, height: cellWidth)
}

4. 設置每個 cell 之間的間距


// 設定每個cell之間的水平間距
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return itemSpace

}

// 設定每個cell之間的垂直間距
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return itemSpace
}

5. 實現UICollectionViewDataSource方法:要先實現UICollectionViewDataSource協議的方法,以提供CollectionView所需的數據。這些方法包括返回節的數量、每個節的項目數和創建單元格的方法。

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

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCellIdentifier", for: indexPath) as! YourCustomCell
// 配置單元格的內容
return cell
}

6. 刷新數據:當數據源發生變化時,確保調用CollectionView的reloadData()方法來刷新視圖,以顯示最新的數據。

2. AlamofireImage

使用說明:

Steps

1. 在 Airtable 上生成獨立的 table

⛳️HomeCollectionViewController

i. 檢查是否為首次運行 App

func checkFirstLaunch() {
// 檢查是否是第一次啟動應用程式:查看 UserDefaults 中是否有存一個 "FirstLaunch : false" 的值
guard let firstLaunch: Bool = UserDefaults.standard.value(forKey: "FirstLaunch") as? Bool else {
// 如果沒有找到 "FirstLaunch" 鍵,表示這是第一次啟動應用程式
print("is first launch")

//使用自定義的 Class Airtable 在 airtable 上新增表格(詳細程式碼見下下段 "Airtable.shared.createNewTable()")
tableName = Airtable.shared.createNewTable()
//顯示文字說明
emptyReminder.isHidden = false
//將新建立的 tableName 存入 UserDefaults 中,方便後續存取
UserDefaults.standard.setValue(tableName, forKey: "TableName")
//在 UserDefaults 中建立一個 "FirstLaunch: false" 的值,下次再啟動 app 時就不會再重新跑這段程式建立新表格了。
UserDefaults.standard.setValue(false, forKey: "FirstLaunch")

//guard let 最後要記得 return!
return
}
}

ii. 透過 Airtable API 建立新表格

> 建立 struct 表格欄位

import Foundation

struct AirtableColumn: Codable {
let name: String
let fields: [Field]
let id: String?
}

struct Field: Codable {
let name: String
let type: String
let options: Options?
}

struct Options: Codable {
let timeZone: String?
let dateFormat: DateFormat?
let timeFormat: TimeFormat?
}

struct DateFormat: Codable {
let name: String
let format: String
}

struct TimeFormat: Codable {
let name: String
let format: String
}

> Airtable.shared.createNewTable()

import UIKit

class Airtable {
// 創建 Airtable 類別的共享實例。在其他地方就可以模仿官方程式碼使用 Airtable.shared.xxxxx 來呼叫此 class 中設置的 function。
static let shared = Airtable()

// 創建新的 Airtable 表格,並回傳表格名稱
func createNewTable() -> String {
// 生成一個不重複的表格名稱
let uniqueTableName = UUID().uuidString
//目標 base 的 URL
let urlString = "https://api.airtable.com/v0/meta/bases/appr9MHZqV2sDkSNN/tables"

// 定義表格的列和欄
//*注意這邊 optional 的欄位無法直接省略,而必須一一列出來並設置成 nil
let columns = AirtableColumn(name: uniqueTableName, fields: [
Field(name: "Date", type: "dateTime", options: Options(timeZone: "Asia/Taipei", dateFormat: DateFormat(name: "iso", format: "YYYY-MM-DD"), timeFormat: TimeFormat(name: "24hour", format: "HH:mm"))),
Field(name: "ImageURL", type: "url", options: nil),
Field(name: "Notes", type: "multilineText", options: nil)
], id: nil)

//設置 urlRequest
if let url = URL(string: urlString) {
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("Bearer *****", forHTTPHeaderField: "Authorization")
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

// 將表格結構轉換為 JSON 格式
let encoder = JSONEncoder()
let body = try? encoder.encode(columns)
urlRequest.httpBody = body

//使用 "POST" 開始上傳表格結構
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
//如果成功
if let data {
let responseString = String(data: data, encoding: .utf8)
print("encode response:",responseString)

//解碼回傳的資料儲存 TableID
let decoder = JSONDecoder()
let tableItem = try? decoder.decode(AirtableColumn.self, from: data)
UserDefaults.standard.setValue(tableItem?.id, forKey: "TableID")

//如果失敗
} else {
print("encode error",error)
}
}.resume()

}
//回傳表格名稱
return uniqueTableName
}

2. 檢查 table 有無資料

⛳️HomeCollectionViewController

//創建一個 AirtableRecords 實例,用於儲存從 Airtable 獲取的記錄
var airTableRecords = AirtableRecords(records: [])
//創建一個 fetchCount 變數,用於跟踪獲取記錄的數量
var fetchCount = 0

func updateRecords() {
//重置紀錄的獲取數
fetchCount = 0
// 調用 Airtable 類別的 getRecords 函數,用於獲取記錄(詳細程式碼見下下段 "Airtable.shared.getRecords()")
Airtable.shared.getRecords { records in
if let records {
// 如果成功獲取記錄,更新 airTableRecords 變數以儲存這些記錄
self.airTableRecords = records
DispatchQueue.main.async {
// 更新進度條,顯示進度為 75% (進度條設置請見第 節“設置進度條” )
self.updateProgressBar(progress: 0.75)
// 重新加載集合視圖,以顯示新的記錄
self.collectionView.reloadData()
}

}
}
}

> 建立 struct 資料欄位

import Foundation

struct AirtableRecords: Codable {
let records: [Records]?
}

struct Records: Codable {
var id: String?
var fields: Fields
}

struct Fields: Codable {
let date: String
let imageURL: URL?
let notes: String?

enum CodingKeys: String,CodingKey {
case date = "Date"
case imageURL = "ImageURL"
case notes = "Notes"
}
}

> Airtable.shared.getRecords(completion: )

class Airtable {
static let shared = Airtable()
......

// 函數用於從 Airtable 獲取記錄並返回 AirtableRecords 實例
func getRecords(completion: @escaping (AirtableRecords?) -> ()) {
// 從 UserDefaults 中獲取表格名稱
let tableName = UserDefaults.standard.value(forKey: "TableName") as? String
if let tableName {
//建立 urlRequest
let urlString = "https://api.airtable.com/v0/appr9MHZqV2sDkSNN/\(tableName)?sort%5B0%5D%5Bfield%5D=Date&sort%5B0%5D%5Bdirection%5D=asc"
var urlRequest = URLRequest(url: URL(string: urlString)!)
urlRequest.httpMethod = "GET"
urlRequest.setValue("Bearer *****", forHTTPHeaderField: "Authorization")
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

//使用 "GET" 從 Airtable 獲取紀錄
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let data {
let decoder = JSONDecoder()
do {
// 解碼 JSON 數據並存儲在 airTableRecords 變數中,並使用 clousre 將存好的資料傳給調用方
self.airTableRecords = try decoder.decode(AirtableRecords.self, from: data)
completion(self.airTableRecords)
} catch {
print("decode error:",error)
completion(nil)
}
}

}.resume()

}
}

......
}

3. 生成 collectionView Cell

⛳️HomeCollectionViewController

i. 欄位

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//檢查是否存在 airTableRecords.records
if let records = airTableRecords.records {
// 存在 airTableRecords.records,但是 records 為空,則顯示 emptyReminder
if !(records.count > 0) {
self.emptyReminder.isHidden = false
} else {
// 如果有 reords ,則隱藏 emptyReminder
self.emptyReminder.isHidden = true
}
// 回傳 records.count 數量的 cells
return records.count

//若是不存在 airTableRecords.records
} else {
return 0
}
}

ii. 內容

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

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PostsCollectionViewCell", for: indexPath) as! PostsCollectionViewCell

if let records = airTableRecords.records {
// 如果存在 image URL
if let url = records[indexPath.item].fields.imageURL {
// 從 Airtable 下載圖像(詳細程式碼見下下段 "Airtable.shared.fetchImage()")
Airtable.shared.fetchImage(url: url) { image in
DispatchQueue.main.async {
// 檢查 cell 的標記是否與當前 indexPath 相符,以確保將圖像設置到正確的 cell 上
if cell.tag == indexPath.item {
cell.postImage.image = image
self.fetchCount += 1

// 當所有圖像都下載完成後,更新進度設置為 100%
if self.fetchCount == records.count {
self.updateProgressBar(progress: 1)
}
}
}
}
}
// 設置 cell 的標記,以便在下載完成後確認圖像是否設置到正確的 cell 上
cell.tag = indexPath.item
}
}

return cell
}

> Airtable.shared.fetchImage(url: ,completionHandler:)

class Airtable {
static let shared = Airtable()
......

// 創建 NSCache 來緩存圖像,使用 NSString 類型作為名稱
let imageCache = NSCache<NSString, UIImage>()

func fetchImage(url: URL, completionHandler: @escaping (UIImage?) -> Void) {
// 設置 NSString 作為緩存名稱
let cacheKey = url.absoluteString

// 先檢查圖片是否已經存在於 NSCache 中
if let image = imageCache.object(forKey: cacheKey as NSString) {
// 圖片已存在,則直接將緩存的圖像傳遞回調用者
completionHandler(image)
} else {
// 如果圖片不存在,才執行下載和緩存
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, let image = UIImage(data: data) {
// 將圖像緩存到 NSCache 中,以供將來使用,同時將圖像傳遞回調用者
self.imageCache.setObject(image, forKey: cacheKey as NSString)
completionHandler(image)
} else {
completionHandler(nil)
}
}.resume()
}

}
......
}

4. 調整 CollectionView Cells 大小

⛳️HomeCollectionViewController

i. 設置 flowLayout

//設置初始每一行的 cell 數為 3
var columnCount: Double = 3
//依照 columnCount 設置對應的 cell 間隔
var itemSpace: Double {
switch columnCount {
case 1:
return 4
case 6:
return 1
case 7:
return 0
default:
return 2
}
}


func setupCellSize(columnCount: Double) {

let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout
//計算每個 cell 的寬度,並設置為 cell 的寬度 & 高度(變成正方形)
let width = floor((collectionView.bounds.width - itemSpace * (columnCount-1)) / columnCount)
flowLayout?.itemSize = CGSize(width: width, height: width)
// 設定不使用flowLayout 的估計尺寸,避免衝突
flowLayout?.estimatedItemSize = .zero
// 設定單元格之間的水平間距
flowLayout?.minimumInteritemSpacing = itemSpace
// 設定單元格之間的垂直間距
flowLayout?.minimumLineSpacing = itemSpace
}

ii. 點擊按鈕調整

@IBAction func zoom(_ sender: UIBarButtonItem) {
//設置一個columnCount的範圍,在此範圍內可放大/所小 cells
switch sender.tag {
case 0:
if columnCount < 7 {
columnCount += 1
}
case 1:
if columnCount > 1 {
columnCount -= 1
}
default:
return
}

// 根據更新後的 `columnCount` 重新設置集合視圖中單元格的尺寸。
setupCellSize(columnCount: columnCount)
// 將更新後的 `columnCount` 儲存在 UserDefaults 中,以便之後讀取。
UserDefaults.standard.setValue(columnCount, forKey: "columnCount")

}

5.設置進度條

⛳️HomeCollectionViewController

i. 樣式設置

// 創建一個 UIProgressView
let progressBar = UIProgressView(progressViewStyle: .default)
var progrssValue: Float = 0.0

func createProgressBar() {
// 設定進度條的顏色,初始值為 0
progressBar.trackTintColor = UIColor.lightGray
progressBar.progressTintColor = UIColor.tintColor
progressBar.progress = 0.0

// 計算進度條視圖的高度、寬度、及位置,以使其位於導航欄的底部。
let progressBarHeight: CGFloat = 2.0
let progressBarWidth = navigationController?.navigationBar.frame.width ?? 0.0
let progressBarFrame = CGRect(x: 0, y: navigationController?.navigationBar.frame.height ?? 0.0 - progressBarHeight, width: progressBarWidth, height: progressBarHeight)
progressBar.frame = progressBarFrame

// 將進度條添加到導航欄
navigationController?.navigationBar.addSubview(progressBar)
}

ii. 進度數值設置

func updateProgressBar(progress: Float) {
//如果最新進度 > 當前進度,則更新當前進度至最新進度
if progress >= progrssValue {
progrssValue = progress
}
//設定進度條的進度百分比,並開啟動畫
progressBar.setProgress(progrssValue, animated: true)

//如果進度達到 100%,則 1.2 秒後(使得進度條動畫可以完整跑完)隱藏進度條。
if progrssValue == 1 {
Timer.scheduledTimer(withTimeInterval: 1.2, repeats: false) { _ in
self.progressBar.isHidden = true
}
}
}

6. 畫面的生命週期

⛳️HomeCollectionViewController

i. ViewDidLoad 初始載入

override func viewDidLoad() {
super.viewDidLoad()

// 檢查應用是否是第一次啟動
checkFirstLaunch()

// 從 UserDefaults 中讀取每列 cell 個數。如果存在的話則依照此數值設置畫面
if let savedColumnCount = UserDefaults.standard.value(forKey: "columnCount") as? Double {
columnCount = savedColumnCount
}
setupCellSize(columnCount: columnCount)

//創建進度條
createProgressBar()

//UICollectionView 自帶的程式碼,用以註冊集合視圖的 cell 類型
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}

ii. viewDidDisappear 離開頁面

override func viewDidDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//隱藏進度條
DispatchQueue.main.async {
self.progressBar.isHidden = true
}

}

iii. viewWillAppear 頁面顯示前

override func viewWillAppear(_ animated: Bool) {
//將進度條直接歸零
progrssValue = 0
progressBar.setProgress(0, animated: false)
}

iv. viewDidAppear 頁面顯示後

//設置一個 Bool 值檢查是否有收到 Notification Center 通知
var receivedNotification: Bool = false

override func viewDidAppear(_ animated: Bool) {
//顯示進度條並隱藏 emptyReminder 標籤
emptyReminder.isHidden = true
progressBar.isHidden = false

updateProgressBar(progress: 0.2)

//標記此處尚未收到 NotificationCenter 通知
receivedNotification = false


// 註冊 NotificationCenter 的觀察者,監聽名稱為 "DataReceived" 的通知
NotificationCenter.default.addObserver(forName: NSNotification.Name("DataReceived"), object: nil, queue: nil) { notification in
// 如果跑到這裡,表示已接收到通知,故設定 receivedNotification 為 true。
self.receivedNotification = true

// 更新進度條
DispatchQueue.main.async {
self.updateProgressBar(progress: 0.3)
}

// 如果通知包含 AirtableRecords,則將其資料存儲到 airTableRecords 變數中
if let records = notification.object as? AirtableRecords {
self.airTableRecords = records
DispatchQueue.main.async {
// 更新進度條,將 fetchCount 並重新抓取 AirtableRecords 資料
self.updateProgressBar(progress: 0.7)
self.fetchCount = 0
print("reset fetchCount",self.fetchCount)
self.updateRecords()
}
}
}


//因為 NotificationCenter 通知要等待回傳方的 closure 跑完,會有一點延遲,故這邊設置一個 Timer 於 2.4 秒後進行檢查
Timer.scheduledTimer(withTimeInterval: 2.4, repeats: false) { _ in
DispatchQueue.main.async {
// 更新進度條
self.updateProgressBar(progress: 0.4)
//如果 Timer 結束後仍未收到 NotificationCenter 通知,則自行刷新頁面
if self.receivedNotification == false {
self.updateRecords()
} else {
// 如果已經收到通知,則已依據上方程式碼重新抓取 AirtableRecords 資料了,不需再刷新頁面,直接將進度條設定為 1 即可。
self.updateProgressBar(progress: 1)
}
}
}

7. 查看 Collection View Cells

⛳️HomeCollectionViewController

override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

if let controller = storyboard?.instantiateViewController(identifier: "\(NoteEditorViewController.self)") as? NoteEditorViewController {

if let records = airTableRecords.records {
// 將所點擊 cell 的資訊傳到下一頁
controller.recordID = records[indexPath.item].id
controller.selectedRecordField = records[indexPath.item].fields
controller.detailImageURL = records[indexPath.item].fields.imageURL
controller.detailNote = records[indexPath.item].fields.notes
//點擊 cell 查看過去紀錄,並非創建新紀錄,故 newRecord = false
controller.newRecord = false
//跳轉頁面
navigationController?.pushViewController(controller, animated: true)
}
}
}

8. 顯示筆記詳情

⛳️NoteEditorViewController

import AlamofireImage

override func viewDidLoad() {
super.viewDidLoad()

......
//檢視既有筆記、而非新增
if newRecord == false {
// 檢查 detailImageURL 變數,如果有值,使用 Alamofire 加載並顯示圖片
if let detailImageURL {
mainImageView.af.setImage(withURL: detailImageURL)
backgroundImageView.af.setImage(withURL: detailImageURL)

// 檢查 detailNote 變數,如果有值,將其設置為 inputTextView 的文本
if let detailNote {
inputTextView.text = detailNote
}

// 將 inputTextView 設為不可編輯
inputTextView.isEditable = false

// 顯示工具按鈕
for button in toolButtons {
button.isHidden = false
}
}

//如果是從新增筆記的頁面進來的,則設置 textView 為暫存的 noteString 並隱藏工具按鈕
} else {
if let noteString {
inputTextView.text = noteString
}
for button in toolButtons {
button.isHidden = true
}
}


......


}

9. 編輯筆記內文

⛳️NoteEditorViewController

當點擊按鈕時,如果 inputTextView 是不可編輯的,則將其切換為可編輯狀態;如果 inputTextView 正在編輯中,則將其切換為不可編輯狀態,同時保存編輯的結果。

@IBAction func edit(_ sender: UIBarButtonItem) {
//如果 inputTextView 是不可編輯的
if editStatus == false {
//將其切換為可編輯狀態,並呼喚出鍵盤
inputTextView.isEditable = true
inputTextView.becomeFirstResponder()
//變更按鈕圖示
sender.image = UIImage(systemName: "checkmark")
//切換 editStatus 為 true
editStatus.toggle()

} else {
//切換 editStatus 為 false
editStatus.toggle()
//將其切換為不可編輯狀態
inputTextView.isEditable = false
//更改按鈕圖示
sender.image = UIImage(systemName: "square.and.pencil")

//將編輯後的內容存入 noteString
let noteString = inputTextView.text
//確認有收到這筆紀錄的 ID 以極其欄位資料(從上一頁傳值)
if let recordID, let selectedRecordField {
//創建一個新的 AirtableRecords 紀錄新的值
let record = AirtableRecords(records: [Records(id: recordID, fields: Fields(date: selectedRecordField.date, imageURL: selectedRecordField.imageURL, notes: noteString))])
// 在 Airtable 上編輯原有資料(詳細程式碼見下下段 "Airtable.shared.sentEditedRecord()")
Airtable.shared.sentEditedRecord(record: record)
}
}
}

> Airtable.shared.sentEditedRecord(record:, completion:)

class Airtable {
static let shared = Airtable()
......

//於參數中傳入編輯後的內容
func sentEditedRecord(record: AirtableRecords) {
// 從 UserDefaults 中檢查是否存在表名(tableName)
if let tableName = UserDefaults.standard.value(forKey: "TableName") {
// 設置 URLRequest,httpMethod 為 "PATCH" (僅編輯單一欄位、不替換其他欄位資料)
var urlRequest = setupURLRequest(tableName, httpMethod: "PATCH")

//encode 為 JSON 格式後上傳
let encoder = JSONEncoder()
let body = try? encoder.encode(record)
urlRequest.httpBody = body

URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let data {
let responseString = String(data: data, encoding: .utf8)
print("editing encode response:",responseString)
} else {
print("editing encode error",error)
}
}.resume()

}
}

......
}

10. 分享紀錄

⛳️NoteEditorViewController

@IBAction func share(_ sender: Any) {

// 創建一個圖像渲染器,用於繪製當前 mainImageView 的內容。
let renderer = UIGraphicsImageRenderer(size: mainImageView.bounds.size)

//創建一個 image,將 mainImageView 上所有元件繪製到此 image 上
let image = renderer.image { context in
mainImageView.drawHierarchy(in: mainImageView.bounds, afterScreenUpdates: true)
}

//創建一個 string,將筆記內文複製到此 string 中
let string = detailNote ?? ""

//顯示 UIActivityViewController,用以分享剛剛所創建的 image 及 string
let controller = UIActivityViewController(activityItems: [image,string], applicationActivities: nil)
present(controller, animated: true)

}

11. 移除整筆資料

⛳️NoteEditorViewController

@IBAction func remove(_ sender: Any) {

// 創建一個警示對話框,讓用戶確認是否要刪除記錄
let alertController = UIAlertController(title: "Delete Alert", message: "This record will be deleted permanently.", preferredStyle: .alert)
//取消刪除 > 不做任何改變
let cancelAction = UIAlertAction(title: "CANCEL", style: .cancel)
//確認刪除 > 在 Airtable 上移除該條筆記內容,並回到首頁
let confirmAction = UIAlertAction(title: "DELETE", style: .default) {_ in
print("confirm delete")
//確認該筆記的 id,以便傳送刪除要求
if let id = self.recordID {
// 在 Airtable 上移除該條筆記(詳細程式碼見下下段 "Airtable.shared.removeRecord()")
Airtable.shared.removeRecord(id: id)
}
self.navigationController?.popToRootViewController(animated: true)
}
//將以上兩個按鈕加到警示對話框上
alertController.addAction(cancelAction)
alertController.addAction(confirmAction)
//顯示警示對話框
present(alertController,animated: true,completion: nil)
}

> Airtable.shared.removeRecord(id: )

class Airtable {
static let shared = Airtable()
......
func removeRecord(id: String) {
//從 UserDefaults 中讀取表格名稱
if let tableName = UserDefaults.standard.value(forKey: "TableName") {
//在 url 後綴加上要刪除的筆記 ID
let urlSuffix = "\(tableName)/\(id)"
//送出 "Delete" 請求
var urlRequest = setupURLRequest(urlSuffix, httpMethod: "Delete")

URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let data {
let responseString = String(data: data, encoding: .utf8)
print("deleted encode response:",responseString)
} else {
print("deleted encode error",error)
}
}.resume()

}

}
......
}

--

--