第十一章——編輯UITableView【譯】

在上一章中,您創(chuàng)建了一個在 UITableView 中顯示 Item 實例列表的應(yīng)用程序。 下一步是允許用戶與表進(jìn)行交互——添加,刪除和移動行。 圖11.1顯示了本章結(jié)束后的 Homepwner

圖11.1 編輯模式下的Homepwner

編輯模式

UITableView 有一個 editing 屬性,當(dāng)該屬性設(shè)置為 true 時,UITableView 進(jìn)入編輯模式。 表視圖處于編輯模式后,表的行可由用戶操縱。 根據(jù)表視圖的配置方式,用戶可以更改行的順序,添加行或刪除行。(編輯模式不允許用戶編輯行的文本內(nèi)容。)

但首先,用戶需要一個將 UITableView 置于編輯模式的方法。 現(xiàn)在,您將在表的 頭部視圖(header view) 中添加一個按鈕。 頭部視圖顯示在表格的頂部,對于添加 section 范圍內(nèi) 或 表范圍內(nèi) 的標(biāo)題和控件很有用。它可以是任何 UIView 實例。

請注意,表視圖有兩種 “header” : table header 和 section header。 同樣,也有 table footer 和 section footer(圖11.2)。

圖11.2 header 和 footer

您正在創(chuàng)建一個頭部視圖。 它將有兩個子視圖并且是 UIButton 的實例:一個用于切換編輯模式,另一個用于在表中添加一個新的 Item。 您可以以編程方式創(chuàng)建此視圖,但在這種情況下,您將在故事板文件中創(chuàng)建這個視圖及其子視圖。

首先,我們設(shè)置必要的代碼。 重新打開 Homepwner.xcodeproj。 在 ItemsViewController.swift 中,實現(xiàn)兩個方法。

class ItemsViewController: UITableViewController {

??var itemStore: ItemStore!

??@IBAction func addNewItem(_ sender: UIButton) {

??}

??@IBAction func toggleEditingMode(_ sender: UIButton) {

??}

現(xiàn)在打開 Main.storyboard。 從對象庫中,將 View 拖動到 prototype cell 上方的表視圖的最頂部。 這將添加視圖作為表視圖的 頭部視圖。 調(diào)整這個視圖的高度大約為 60 點。 (如果你要確切地修改,可以使用尺寸檢查器)。

現(xiàn)在將兩個 Button 從對象庫拖動到頭部視圖。 更改文本并定位它們,如圖11.3所示。 您不需要和圖中一樣精確——您將很快添加約束來定位按鈕。

圖11.3添加按鈕到頭部視圖

選擇這兩個按鈕并打開 自動布局 Align 菜單。 在 Vertically in Container 選擇常量0.確保 Update Frames 設(shè)置為 None,然后單擊 Add 2 Constraints(圖11.4)。

圖11.4 對齊(Align)菜單約束

打開 Add New Constraints 菜單并進(jìn)行配置,如圖11.5所示。 確保在鍵入它們之后保存 前部(leading) 和 尾部(trailing) 約束的值; 有時值不會保存,所以有點棘手。 完成后,單擊 Add 4 Constraints

圖11.5 添加新約束

最后,連接兩個按鈕的動作,如圖11.6所示。

圖11.6連接兩個動作

構(gòu)建并運(yùn)行應(yīng)用程序以查看界面。

現(xiàn)在我們來實現(xiàn) toggleEditingMode(_ :) 方法。 您可以直接修改 UITableViewediting 屬性。 但是, UIViewController 也有 editing 屬性。 UITableViewController 實例自動設(shè)置其表視圖的 editing 屬性以匹配自己的 editing 屬性。 通過在視圖控制器本身設(shè)置 editing 屬性,可以確保界面的其他方面也同時進(jìn)入或離開編輯模式。例子將在第14章中 UIViewControllereditButtonItem 看到。

要設(shè)置視圖控制器的 isEditing 屬性,可以調(diào)用 setEditing(_:animated :) 方法。 在 ItemsViewController.swift 中,實現(xiàn) toggleEditingMode(_ :)

@IBAction func toggleEditingMode(_ sender: UIButton) {
??// If you are currently in editing mode...
??if isEditing {
????// Change text of button to inform user of state
????sender.setTitle("Edit", for: .normal)

????// Turn off editing mode
????setEditing(false, animated: true)
??} else {
????// Change text of button to inform user of state
????sender.setTitle("Done", for: .normal)

????// Enter editing mode
????setEditing(true, animated: true)
??}
}

構(gòu)建并運(yùn)行您的應(yīng)用程序。 點擊 Edit 按鈕,UITableView 將進(jìn)入編輯模式(圖11.7)。

圖11.7 編輯模式下的UITableView

添加行

在運(yùn)行時,有兩個通用的界面用于在表視圖中添加行。

  • 表格視圖cell上方的按鈕:通常用于添加有詳細(xì)視圖的記錄。 例如,在 聯(lián)系人(Contacts) 應(yīng)用中,當(dāng)您遇到新人并想要取消他或她的信息時,您可以點擊一個按鈕。
  • 具有綠色加號的cell:通常用于向記錄添加新字段,例如當(dāng)您要在 聯(lián)系人(Contacts) 應(yīng)用程序中為個人記錄添加生日時。 在編輯模式下,點擊 添加生日(add birthday) 旁邊的綠色加號。

在本練習(xí)中,您將使用第一個選項,并在標(biāo)題視圖中創(chuàng)建一個新按鈕。 當(dāng)點擊此按鈕時,新行將添加到 UITableView

ItemsViewController.swift 中,實現(xiàn) addNewItem(_ :)

@IBAction func addNewItem(_ sender: UIButton) {
??// Make a new index path for the 0th section, last row
??let lastRow = tableView.numberOfRows(inSection: 0)
??let indexPath = IndexPath(row: lastRow, section: 0)

??// Insert this new row into the table

??tableView.insertRows(at: [indexPath], with: .automatic)
}

構(gòu)建并運(yùn)行應(yīng)用程序。 點擊 Add 按鈕,...應(yīng)用程序崩潰。 控制臺告訴您,表視圖有內(nèi)部不一致(internal inconsistency) 異常。

記住,最終,UITableViewdataSource 決定了表視圖應(yīng)該顯示的行數(shù)。 插入新行后,表視圖有六行(原來的五行加上新行)。 當(dāng) UITableView 詢問其 dataSource 的行數(shù)時,ItemsViewController 會咨詢該 store 并返回應(yīng)該有五行。 UITableView 無法解決這種不一致并引發(fā)異常。

您必須確保 UITableView 及其 dataSource 在插入新行之前通過向 ItemStore 中添加 Item 來改變行數(shù)。

ItemsViewController.swift 中,更新 addNewItem(_ :)

@IBAction func addNewItem(_ sender: UIButton) {
??Make a new index path for the 0th section, last row
??let lastRow = tableView.numberOfRows(inSection: 0)
??let indexPath = IndexPath(row: lastRow, section: 0)

??Insert this new row into the table
??tableView.insertRows(at: [indexPath], with: .automatic)

??Create a new item and add it to the store
??let newItem = itemStore.createItem()

??// Figure out where that item is in the array
??if let index = itemStore.allItems.index(of: newItem) {
????let indexPath = IndexPath(row: index, section: 0)
????// Insert this new row into the table
????tableView.insertRows(at: [indexPath], with: .automatic)
??}
}

運(yùn)行應(yīng)用程序。 點擊 Add 按鈕,新行將滑動到表的底部位置。 請記住,視圖對象的作用是將模型對象呈現(xiàn)給用戶; 更新視圖而不更新模型對象沒什么卵用。

現(xiàn)在您可以添加行和 item,您不再需要將五個隨機(jī)項目放入 store 的代碼。

打開 ItemStore.swift 并刪除初始化程序代碼。

init() {
??for _ in 0..<5 {
????createItem()
??}
}

構(gòu)建并運(yùn)行應(yīng)用程序。 首次啟動應(yīng)用程序時,不再有任何行,但您可以通過點擊 Add 按鈕添加一些行。

刪除行

在編輯模式中,帶有減號(如圖11.7所示)的紅色圓圈是刪除控件,然后點擊一個應(yīng)該會刪除該行。 但是,在這一點上,您實際上不能刪除該行。 (嘗試看看。)在表視圖刪除一行之前,它會在其數(shù)據(jù)源上調(diào)用關(guān)于建議刪除的方法,并等待確認(rèn)。

刪除 cell 時,您必須做兩件事情:從 UITableView 中刪除該行,并從 ItemStore 中刪除與之相關(guān)聯(lián)的 Item。 要將其關(guān)閉,ItemStore 必須知道如何從其中刪除對象。

ItemStore.swift 中,實現(xiàn)一個新的方法來刪除特定的 item。

func removeItem(_ item: Item) {
??if let index = allItems.index(of: item) {
????allItems.remove(at: index)
??}
}

你將會實現(xiàn) tableView(_:commit:forRow :),一個 UITableViewDataSource 協(xié)議的方法。(這個方法在 ItemsViewController 上調(diào)用,請記住,盡管 ItemStore 是保存數(shù)據(jù)的地方,而 ItemsViewController 才是表視圖的 dataSource。)

當(dāng)在數(shù)據(jù)源上調(diào)用 tableView(_:commit:forRowAt :) 時,會傳遞兩個額外的參數(shù)。 第一個是 UITableViewCellEditingStyle,在這種情況下,它是 .delete。 另一個參數(shù)是表中行的 IndexPath

ItemsViewController.swift 中,實現(xiàn)此方法使 ItemStore 刪除正確的對象,并通過在表視圖上調(diào)用 deleteRows(at:with :) 方法來確認(rèn)刪除行。

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
??// If the table view is asking to commit a delete command...
??if editingStyle == .delete {
????let item = itemStore.allItems[indexPath.row]
????// Remove the item from the store
????itemStore.removeItem(item)

????// Also remove that row from the table view with an animation
????tableView.deleteRows(at: [indexPath], with: .automatic)
??}
}

構(gòu)建并運(yùn)行應(yīng)用程序,創(chuàng)建一些行,然后刪除一行。 它就會消失。請注意,滑動刪除也可以。

移動行

要更改 UITableView 中的行順序,您將使用 UITableViewDataSource 協(xié)議中的另一個方法——tableView(_:moveRowAt:to :)

要刪除一行,您必須在 UITableView 上調(diào)用 deleteRows(at :),以確認(rèn)刪除。 但是,移動一行不需要確認(rèn):表視圖會自動移動該行,并通過調(diào)用 tableView(_:moveRowAt:to :) 方法來報告數(shù)據(jù)源發(fā)生了移動。 實現(xiàn)此方法來更新數(shù)據(jù)源以匹配新順序。

但是,在實現(xiàn)此方法之前,您需要給 ItemStore 一個方法來更改其 allItems 數(shù)組中 item 的順序。

ItemStore.swift 中,實現(xiàn)這個新方法。

func moveItem(from fromIndex: Int, to toIndex: Int) {
??if fromIndex == toIndex {
????return
??}

??// Get reference to object being moved so you can reinsert it
??let movedItem = allItems[fromIndex]

??// Remove item from array
??allItems.remove(at: fromIndex)

??// Insert item in array at new location
??allItems.insert(movedItem, at: toIndex)
}

ItemsViewController.swift 中,實現(xiàn) tableView(_:moveRowAt:to :) 來更新 store。

override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
??// Update the model
??itemStore.moveItem(from: sourceIndexPath.row, to: destinationIndexPath.row)
}

運(yùn)行你的應(yīng)用程序。 添加幾個 item,然后點擊編輯,并查看每一行側(cè)面的新的重新排序控件(三條水平線)。 觸摸并按住重新排序控件并將該行移動到新位置(圖11.8)。

圖11.8移動一行

請注意,簡單地實現(xiàn) tableView(_:moveRowAt:to :) 會導(dǎo)致重新排序控件出現(xiàn)。 UITableView 可以在運(yùn)行時詢問其數(shù)據(jù)源是否實現(xiàn)了 tableView(_:moveRowAt:to :)。 如果是,則表視圖會在表視圖進(jìn)入編輯模式時添加重新排序控件。

顯示用戶警報

在本節(jié)中,您將了解用戶警報以及配置和顯示用戶警報的不同方法。 用戶警報可以為您的應(yīng)用程序提供更好的用戶體驗,因此您會經(jīng)常使用它們。

警報通常用于警告用戶一個重要的動作即將發(fā)生,同時給他們機(jī)會取消該動作。 當(dāng)您要顯示警報時,您將創(chuàng)建一個具有首選樣式的 UIAlertController 實例。 兩種可用的樣式是 UIAlertControllerStyle.actionSheetUIAlertControllerStyle.alert (圖11.9)。

圖11.9 UIAlertController樣式

.actionSheet 樣式用于向用戶呈現(xiàn)要從中選擇的動作列表。 .alert 類型用于顯示關(guān)鍵信息,要求用戶決定如何繼續(xù)。 這個區(qū)別可能看起來很微妙,但是如果用戶決定取消選擇,或者動作不重要的話,那么一個 .actionSheet 可能是最好的選擇。

您將使用 UIAlertController 來確認(rèn) item 的刪除。 您將使用 .actionSheet 樣式,因為警報的目的是確認(rèn)或取消可能的破壞性操作。

打開 ItemsViewController.swift 并修改 tableView(_:commit:forRowAt :),要求用戶確認(rèn)或取消 刪除 item。

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

??// If the table view is asking to commit a delete command...
??if editingStyle == .delete {
????let item = itemStore.allItems[indexPath.row]

????let title = "Delete \(item.name)?"
????let message = "Are you sure you want to delete this item?"

????let ac = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)

????// Remove the item from the store
????itemStore.removeItem(item)

????// Also remove that row from the table view with an animation
????tableView.deleteRows(at: [indexPath], with: .automatic)
??}
}

在確定用戶想要刪除項目之后,您將創(chuàng)建一個具有適當(dāng)標(biāo)題和消息的 UIAlertController 實例,描述將要執(zhí)行的操作。 此外,您為該警報指定 .actionSheet 樣式。

顯示警報時用戶可以選擇的動作是 UIAlertAction 的實例,您可以添加多個操作,而不管警報的樣式。 使用 addAction(_ :) 方法將動作添加到 UIAlertController 中。

tableView(_:commit:forRowAt :) 中的動作工作表中添加必要的操作。

...
let ac = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)

let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
??ac.addAction(cancelAction)

let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: { (action) -> Void in
??// Remove the item from the store
??self.itemStore.removeItem(item)

??// Also remove that row from the table view with an animation
??self.tableView.deleteRows(at: [indexPath], with: .automatic)
})
ac.addAction(deleteAction)
...

第一個動作的標(biāo)題為 “Cancel”,并使用 .cancel 樣式創(chuàng)建。.cancel 樣式會顯示為標(biāo)準(zhǔn)藍(lán)色字體。 此操作將允許用戶退出刪除項目。 處理程序參數(shù)允許在發(fā)生該動作時執(zhí)行閉包。 因為不需要其他動作,所以將 nil 作為參數(shù)傳遞。

第二個動作的標(biāo)題為 “Delete”,并使用 .dructructive 樣式創(chuàng)建。 因為破壞性的行為應(yīng)該被明確標(biāo)注和注意到,.dructructive 風(fēng)格會產(chǎn)生明亮的紅色文字。 如果用戶選擇此動作,則需要刪除 item 和表視圖 cell。 這一切都在傳遞給動作的構(gòu)造器的 'handle' 閉包中完成。

現(xiàn)在已經(jīng)添加了動作,可以向用戶顯示警報控制器。 因為 UIAlertControllerUIViewController 的子類,所以您可以使用 模態(tài)(modally) 將其呈現(xiàn)給用戶。 模態(tài)視圖控制器(modal view controller) 接管整個屏幕,直到其完成工作。

要以視圖方式呈現(xiàn)視圖控制器,您可以在其視圖位于屏幕上的視圖控制器上調(diào)用 present(_:animated:completion :)。 要呈現(xiàn)的視圖控制器被傳遞給它,并且該視圖控制器的視圖接管屏幕。

...
let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: { (action) -> Void in
??// Remove the item from the store
??self.itemStore.removeItem(item)

??// Also remove that row from the table view with an animation
??self.tableView.deleteRows(at: [indexPath], with: .automatic)
})
ac.addAction(deleteAction)

// Present the alert controller
present(ac, animated: true, completion: nil)
...

構(gòu)建并運(yùn)行應(yīng)用程序并刪除一個 item。 將提供一個動作以供您確認(rèn)刪除(圖11.10)。

圖11.10 刪除 item

設(shè)計模式

設(shè)計模式(design pattern) 解決了常見的軟件工程問題。 設(shè)計模式不是代碼的實際代碼段,而是可以在應(yīng)用程序中使用的抽象概念或方法。 良好的設(shè)計模式是任何開發(fā)人員有價值和強(qiáng)大的工具。

在開發(fā)過程中始終如一地使用設(shè)計模式可以減少解決問題的精神開銷,從而可以更輕松,快速地創(chuàng)建復(fù)雜的應(yīng)用程序。 以下是您已經(jīng)使用的一些設(shè)計模式:

  • 委托模式(Delegation):一個對象將某些功能委托給另一個對象。 當(dāng)文本字段的內(nèi)容更改時,您通過 UITextField 使用委托來通知。
  • 數(shù)據(jù)源模式(Data source):數(shù)據(jù)源與委托類似,但不是對另一個對象做出反應(yīng),而是在請求時,數(shù)據(jù)源負(fù)責(zé)向另一個對象提供數(shù)據(jù)。 您之前已將數(shù)據(jù)源模式與表視圖一起使用過:每個表視圖都有一個數(shù)據(jù)源,它至少負(fù)責(zé)告訴表視圖要顯示多少行以及每個索引路徑應(yīng)顯示哪個單元格。
  • Model-View-Controller:應(yīng)用程序中的每個對象都可以滿足三個角色之一。 模型對象是數(shù)據(jù)。 視圖顯示UI。 控制器提供將模型和視圖結(jié)合在一起的膠水。
  • 目標(biāo) - 動作對(Target-action pairs):當(dāng)特定事件發(fā)生時,一個對象調(diào)用另一個對象的方法。 目標(biāo)是有一個方法被調(diào)用的對象,動作是被調(diào)用的方法。 例如,您使用具有按鈕的目標(biāo)動作對:當(dāng)觸發(fā)事件發(fā)生時,將會在另一個對象(通常是視圖控制器)上調(diào)用一個方法。

蘋果在使用這些設(shè)計模式時非常一致,因此了解和識別它們非常重要。 繼續(xù)閱讀這本書,留意這些模式! 認(rèn)識它們將幫助您更輕松地學(xué)習(xí)新的課程和框架。

青銅挑戰(zhàn):重命名刪除按鈕

刪除行時,會出現(xiàn)一個確認(rèn)按鈕,標(biāo)有 Delete。 嘗試將此按鈕的標(biāo)簽更改為 Remove

白銀挑戰(zhàn):防止重新排序

表格視圖總是顯示一個最后一行,表示 “No more items!”(這部分挑戰(zhàn)與上一章的挑戰(zhàn)是一樣的,如果你已經(jīng)完成了,你可以復(fù)制你之前的代碼 )現(xiàn)在,令最后一行不能移動。

黃金挑戰(zhàn):真正防止重新排序

完成銀牌挑戰(zhàn)后,您可能會注意到,即使您不能移動 No more items! 行本身,您仍然可以拖動其下的其他行。 做到這一點——無論什么——No more items! 行永遠(yuǎn)不會被淘汰出最后的位置。 最后,使它不可被刪除。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內(nèi)容