iOS APP Extension - Custom KeyBoard文档

来自官方文档的内容

App Extension Programming Guide - Custom KeyBoard

自定义键盘

自定义键盘能够为那些需求新颖的输入方式或者提供系统输入法所不支持的语言功能的用户提供。自定义键盘的核心功能很简单:响应按键、手势或其它输入事件,在当前文本输入对象的文本插入点输入未归类的NSString对象。

阅读开始前
首先要确定你你所开发的自定义键盘确实是系统级别范围所需要的,如果你只是想为你自己app内部提供一个自定义的键盘,或者只是为系统键盘添加一些自定义按键,那么iOS SDK 提供了更好的方式。 关于自定义输入视图和输入辅助视图可以参考文档Custom Views for Data InputAbout Text Handling in iOS

当用户选择了自定义键盘后,这个自定义键盘将会由这个用户所有的APP使用。为此,你创建的键盘至少要包含一些基本功能。最重要的是你的键盘必须允许用户切换到其他键盘。

理解用户对键盘的需求

想要了解用户对系统键盘的需求,可以学习系统键盘-- 快速,响应迅速且功能强大。不会因为信息或者请求打断用户。如果你一些需要用户交互的功能,请不要添加在键盘上,而是添加到键盘上所包含的应用中。

iOS用户所想要的键盘功能

有一个功能是所有自定义键盘需要提供且用户也需要的:切换到其他键盘。在系统键盘中,这个功能是由一个地球按钮的按键提供的。在iOS8及以后,系统提供了抓们的API来完成这个功能,参考Providing a Way to Switch to Another Keyboard.

系统键盘会基于当前文本输入对象的UIKeyboardType特性来呈现合适的键位和相应的布局。比如需要输入一个邮箱地址,系统键盘的句号按键就会改变:长按的时候能够提供多个顶级域名后缀选择。设计你的自定义键盘的时候考虑一下这些特性。

自动大写:在标准文本输入区域,对于首字母大写敏感的语言,应该提供自动首字母大写。

更多这类功能如下:
1、基于UIKeyboardType特性的键盘布局。
2、自动校正与建议。
3、自动首字母大写。
4、两个空格后自动添加句号。
5、大写锁定支持。
6、键帽美化。
7、表意语言的多级输入。

你自己决定是否要加入这些特性。系统没有为这些功能提供专有的API,所以加入这些让你更具备竞争力。

一些系统键盘具备的功能不能在自定义键盘上实现

你的键盘不能访问设置选项中大部分的通用键盘设置(设置>通用>键盘),比如自动大写、大写锁定。同样你的自定义键盘也不能访问字典还原功能(设置>通用>还原>还原键盘字典)。如要满足用户的灵活需求,请创建一个标准的设置选项,参考 Implementing an iOS Settings Bundle in Preferences and Settings Programming Guide。与你自定义键盘有关的自定义设置选项就会出现在设置选项中的键盘区域。

有一些文本输入对象是自定义键盘没有权限进行输入访问的。密码等安全相关的文本输入对象:比如secureTextEntry属性被设置位YES,输入的信息用点来显示。

当用户在安全文本对象(比如密码框)输入时,系统会会临时的用系统键盘来替换你的自定义键盘。当用户在非安全输入对象输入的时候,你的自定义键盘就会恢复。

自定义键盘同样不能用在拨号输入对象,比如通讯录中的号码输入框。这些输入对象专门用于由电信运营商指定的一组数字、字符,并由以下一个或者另外一个键盘类型来标示:

当用户在电话拨号输入对象输入的时候,系统会用合适的系统键盘临时的替换掉你的自定义键盘。当用户再次使用其他标准输入对象的时候,你的自定义键盘又会恢复。

APP的开发者可以选择在自己的APP中不使用所有的自定义键盘。比如银行类App或者遵循美国HIPAA隐私规范的App。这类App实现了UIApplicationDelegate协议的 application:shouldAllowExtensionPointIdentifier:方法,这用就只会使用系统的键盘。

由于自定义键盘只能在 UIInputViewController对象的主视图内部进行绘制显示,所以它不能选择文本。文本的选择是由使用键盘的应用程序控制的。如果App提供了编辑菜单(比如复制、剪切和粘贴),键盘是没有权限访问的。自定义键盘是不能提供光标位置附近的inline自动校正功能。

在iOS8.0中自定义键盘和其他应用程序扩展一样,不能访问设备话筒,因此不能实现语音输入。

最后,自定义键盘按键长按显示额外键位信息的视图是不能超过自定义键盘原始视图的顶部,系统键盘却可以。

自定义键盘API入门

这一节内容让你快速了构建自定义键盘的APIs。下图展示了一些关于键盘运行中的一些比较重要的对象以及在标准开发流程中的位置。


image.png

Custom Keyboard template自定义键盘模板(在iOS“Application Extension” target template group)包含了一个 UIInputViewController的子类,这个子类作为你键盘的初始视图控制器。这个模板也包含了一个基本的按键实现“切换下一个键盘”,它调用了UIInputViewController的的 advanceToNextInputMode方法。向inputviewcontroller的原始视图中添加视图、手势等。向其他应用程序扩展一样,对象没有window,也没有根视图控制器。

模板的info.plist文件中预先配置好了键盘的基本参数。查看在键盘target的info.plist文件下对应的NSExtensionAttributeskey键信息。这些关于键盘配置信息的key请查看Configuring the Info.plist file for a Custom Keyboard

默认情况下,键盘是没有网络访问权限的,也不能和它的容器App共享同一个缓存空间。如果需要实现这些功能,请在info.plist文件中设置RequestsOpenAccessYES。在这之后,会扩展键盘的沙盒。更多参考Designing for User Trust

一个input view controller与文本输入对象内容的交互需要遵循多个协议:

[self.textDocumentProxy insertText:@"hello "]; // Inserts the string "hello " at the insertion point
[self.textDocumentProxy deleteBackward];       // Deletes the character to the left of the insertion point
[self.textDocumentProxy insertText:@"\n"];     // In a text view, inserts a newline character at the insertion point
NSString *precedingContext = self.textDocumentProxy.documentContextBeforeInput;
  • 拿到文本内容之后,你就可以决定删除多少内容:比如单个字符、或者文本之后的全部空格。

  • 如果你想要删除整个的语义部分,例如一个单词、一个语句或者一个段落,参考 CFStringTokenizer Reference以及相关的文档中的功能函数。注意,每种语言的语义是有区别的。

  • 调用UITextDocumentProxy的协议方法adjustTextPositionByCharacterOffset:让你控制好光标插入位置,比如要删除更早之前输入的字符。
    举个栗子

- (void) deleteForward {
    [self.textDocumentProxy adjustTextPositionByCharacterOffset: 1];
    [self.textDocumentProxy deleteBackward];
}
  • 通过实现UITextInputDelegate方法来响应当前活跃文本对象的内容变化或者插入位置的user-initiated变化。

通过UIKeyboardType属性,为当前文本输入对象展现合适的键盘布局。为每种你所支持的特性,相应的对初始视图做出改变。

要支持两种或者更多的语言,你有两个选择:
1、为每一种语言创建一个键盘,每个键盘单独的作为容器App的一个target。
2、创建一个多语言支持的键盘,根据需要动态的切换合适的语言。想要动态切换主语言,使用UIInputViewController类的primaryLanguage属性。

使用哪种方式创建,处决于你支持的语言种类多少以及用户的实际体验。

所有的自定义键盘(和它的RequestsOpenAccess键的值没有关系)都能通过UILexicon类来访问基本的自动校正词典。请充分利用这个类以及你自己提供的自动校正词典来为用户输入文本提供建议和校正。

UILexicon类的词汇来源包过:

  • 从用户地址簿获取的不成对的姓和名
  • 设置- 通用 - 键盘 - 自定义短语(Settings > General > Keyboard > Shortcuts list )
  • 通用字典

你可以使用自动布局来调整自定义键盘的主视图的高度。默认情况下,自定义键盘的尺寸大小是和系统键盘的大小一致,都是根据屏幕尺寸和方向来决定。系统会把自定义键盘的宽度设置为当前屏幕的宽度。想调整键盘的高度,那就改变主视图的高度约束。
举个例子:

CGFloat _expandedHeight = 500;
NSLayoutConstraint *_heightConstraint = 
    [NSLayoutConstraint constraintWithItem: self.view 
                                 attribute: NSLayoutAttributeHeight 
                                 relatedBy: NSLayoutRelationEqual 
                                    toItem: nil 
                                 attribute: NSLayoutAttributeNotAnAttribute 
                                multiplier: 0.0 
                                  constant: _expandedHeight];
[self.view addConstraint: _heightConstraint];

注意
在iOS8.0之后当自定义键盘的主视图初始化之后,你可以在任意时候改变它的高度。

自定义键盘开发的要点

两个要点在开发自定义键盘的时候需要注意:
1、信任,你的自定义键盘能够让你访问用户的输入内容,所以信任是你和用户之间最重要的一点。
2、允许“切换下一个键盘”按钮 让用户能够切换到下一个键盘是键盘的一个基本功能。你必须提供这个功能。

为用户信任而做设计

你创建一个自定义输入法优先考虑到的应该是如何建立和维持良好的用户信任,
这种信任处决于你对隐私政策理解的最佳做法并且知道如何去实现。

注意
This section provides guidelines to help you create a custom keyboard that respects user privacy, in terms of factors under your control and described here as responsibilities. To learn about iOS program requirements, read App Store Review Guidelines, iOS Human Interface Guidelines, and iOS Developer Program License Agreement, all linked to from Apple’s App Review Support page. Also review Supporting User Privacy in App Programming Guide for iOS.

对于键盘,以下三个领域对于建立和维护用户信任尤为重要:

  • 键盘输入数据的安全性。 用户希望他们通过键盘输入的数据写入文档或者文本输入框,而不是保存在某个服务器或者被用于用户不可知的地方。

  • 合理的并且最小化的使用用户数据。如果你的键盘使用了其他用户的数据比如定位服务、通讯录,那么你有责任需要向用户解释这样做给用户带来的好处。

  • 精准 输入事件转换为输入文本的准确性并不是用户隐私该做的,但是它对用户的信任产生影响:每一个文字的输入,都能让用户看到你代码的准确性。

信任设计的过程中,首先要考虑的就是open access(完全访问权限)。尽管对于自定键盘来说open access能够让你做到很多事情,这同时也意味着你要承担更多的责任。

下表列出了标准和开启open access(开启网络访问)键盘对应的功能和隐私责任

open access 能力和限制 隐私注意事项
关闭(默认) 1、自定义键盘拥有基本键盘的所有功能
2、可以访问通用校正词典和输入建议
3、访问settings中的短语列表
4、与容器App没有共享缓存空间
5、除了键盘自己的容器App以外没有文件访问权限
6、不能直接或者间接参与iCloud、Game Center或者App内购买
用户知道通过键盘输入的信息仅用于当前APP
开启 1、拥有标准自定义键盘的所有功能
2、用户允许的情况下访问定位服务和通讯录
3、键盘和它的容器App拥有一个共享存储空间
3、键盘能够发送键盘输入信息和其他输入时间给服务器端处理
4、容器App能够为键盘的自定义自动校正词典提供编辑界面
5、通过容器App,键盘能够使用iCloud来确保相关设置和自动校正词典能够更新到所有设备上
6、通过容器App,键盘能够参与Game Center和App内购买
7、如果键盘支持移动设备管理(DMD),那么键盘也能够与受控App协同工作
1、用户知道通过键盘输入的信息能被开发者拿到
2、你必须遵守 应用审核指南iOS程序开发者协议中的联网键盘开发指导,在App Review Support页面中可以找到。

如果你开发的键盘没有开启open access,那么系统就会确保在任何位置键盘的输入信息都不会被发送给你。如果你只是想提供一款基本功能的键盘,那么就使用标准自定义键盘。
由于沙盒的限制,非联网功能的键盘能够让你在满足苹果的数据隐私准则和获取用户信任上取得领先。

如果你开启了完全访问(详情见 Configuring the Info.plist file for a Custom Keyboard),更多的功能对你开放同样也意味着更多的责任需要你承担。

注意
提交一个完全访问权限的第三方键盘到App Store,你必须遵守 App Review Support页面的所有相关准则。

作为开发者,每个与open access有关系的键盘功能都有相对应的你需要承担的责任。最大程度的去尊重用户数据,并且不要把这些数据用于用户所不知道的地方。

功能 用户示例 开发者的责任
与容器App共享存储空间 键盘的输入法自动校正词典的UI管理 考虑到自动校正数据属于用户隐私数据,请不要把发送到服务器用于任何用户所不知道的目的
发送键盘输入数据到你的服务器 通过开发者的计算资源来增强触摸事件或者输入预测 除非有必要为用户提供服务,不要存储用户的音频数据或者键盘输入。
基于互联网支持的动态自动校正 人名、地点、热点事件等加入到自动校正词汇中 不要把用户的身份和相关的信息关联起来用于任何用户未知的目的
通讯录访问 将通讯录中的人名、地点和电话号码等和用户有关的词汇加入到自动校正词典中 不要在用户不知道的情况下将通讯录中的数据用于任何目的
访问定位服务 将地理位置附近相关的位置名词等加入到校正词汇中 不要在后台使用定位。不要在用户不知道的情况下发送定位数据到服务器

使用了open access的自定义键盘和它的容器App是能够将键盘输入数据发送到你的服务器,并且能让你使用自己的计算资源来实现诸如触摸事件处理和输入法预测等功能。如果你使用了这些功能,那么当你接收到的键盘、音频数据已经远超与你向用户提供的服务时你不应该存储这些数据。使用这些open access键盘功能是也请遵守相关要求。

提供切换到其他键盘的方式

当有不止一个输入法可以使用的时候,系统键盘就会有一个小地球按键让用户切换输入法。你的自定义键盘也需要提供一个类似的切换按键。

系统的切换输入法按键

通过访问UIInputViewController类的needsInputModeSwitchKey属性来查看你是否需要在的键盘上显示切换键盘的按键。如果返回值为true,那么你的键盘需要显示这个按键。

让系统切换键盘,请使用UIInputViewController类的advanceToNextInputMode方法。系统将会从列中选择适当的输入法进行切换。这个API不会获取或者选择特定的键盘数据。

Xcode的自定义键盘模板以及包含了调用advanceToNextInputMode方法的按钮。不过为了更好的体验,你最好把这个按钮替换为类似系统小地球按钮的按键。

开始自定义键盘的开发

这一节内容你将要开始学习如何按照你的目标去创建、配置自定义键盘并且运行在模拟器或者真机设备上。同样也要了替换系统键盘需要了解的一些UI因素。

使用Xcode自定义键盘模板

创建一个自定义键盘和它的容器App会与创建其他应用程序扩展少有不同。这一节会带你一步步创建并运行一个键盘。

在容器App中创建自定义键盘

  1. Xcode中选择 File>New>Project,在iOS Application template group中选择Single View Application template
  2. 点击下一步。
  3. 起一个工程名
  4. 选择你要保存工程的位置,然后点击创建。
    到此,你已经创建了一个空的app项目,现在需要包含一个键盘target
  5. 选择Flie>New>Target 在iOSApplication Extension target template group选择Custom Keyboard template,然后点击下一步。
  6. 为你的键盘起一个名字。
  7. 确保ProjectEmbed in Application弹出菜单显示的是容器App的名字,然后点击完成。如果弹出激活新键盘,点击激活。

你现在能够选择自定义键盘组名字,它会出现在settings已购买键盘列表中。

自定义键盘组的名字
1、在工程项目的导航栏中,定位到Supporting Files文件夹下选择容器App的Info.plist文件。可以查看和编辑相关的配置信息。
2、鼠标移到Bundle name这行,点击+号按钮,创建一行新的键值对属性。
3、在键属性栏中添加Bundle display name,然后回车。
4、双击对应的值输入框,输入你想要的键盘组名称。
5、保存文件(File>Save)。

下表列出了容器App和自定义键盘可以在Info.plist中配置的UI字符串属性。

iOS用户界面文字 Info.plist 键
系统settings键盘列表的键盘组名称 容器App的info.plist的Bundle display name
settings中显示的键盘名字
在全局键盘列表中显示的名字
键盘target的Info.plist中的Bundle display name

请注意:

在系统设置的键盘列表中名称的显示:如果键盘的info.plist文件的display name和容器App的info.plist文件的display name不一致,那么settings列表的键盘的显示格式就是keyboardName - hostAppName
反之,如果display name一致,那么settings列表键盘名称显示就是display name

现在你已经能够在模拟器或者真机上运行一个系统初始的基本键盘了,下面开始进一步探索其他功能。

运行自定义键盘并连接到Xcode的调试器上

1、在Xcode中,在键盘VC的代码中设置好断点。
2、确认在Xcode工具栏上选择的当前scheme指定为键盘的方案并且选择了模拟器或者一个真机设备。
3、选择 Product > Run,或者点击Xcode项目窗口左上方播放按钮。Xcode让你选择一个host APP。选择一个可以输入文本段的应用,比如通讯录或者Safari浏览器。(这里只是对键盘进行调试,不一定必须用你的容器App,只要是能够提供本文输入弹出键盘的APP都可以用来调试自定义键盘)。
4、点击Run
Xcode 在指定的App上运行。如果你是第一次在你的模拟器或者真机上运行你的键盘扩展程序,使用下面步骤在settings中启用你的键盘:

  • Settings > General > Keyboard > Keyboards
  • 点击添加新的键盘。
  • 在已购买键盘组中,点击你的键盘名,弹出一个模态视图来开启你的键盘。
  • 点击开启键盘,弹出一个警告。
  • 在警告视图中,点击天机键盘来完成新键盘的添加。最后点击完成。

5、在模拟器或者真机中,唤起你的自定义键盘。
在任意的有可以输入文本区域的App或者Spotlight中都可以唤起键盘,接下来就是切换到你的自定义键盘。
现在你能看到你的自定义键盘了,但是在调试器还没连接。从模板创建的标准系统键盘只有一个Next Keyboard button按钮,让你可以切换到上一个使用的输入法。
确保你的键盘已经激活了,那么继续。
6、关闭键盘(这样在第8步能够在重新唤起键盘的时候调用viewDidLoad断点)。
7、在Xcode中,选择Debug>Attach to Process > By Process Identifier(PID) or Name
在出现的输入框中,输入创建键盘时的键盘扩展的名称(包过空格)。默认这个名字一般都是App扩展在项目导航窗口的group name
8、点击Attach
9、在任意模拟器或者你使用的真机中,点击文本输入框唤起键盘。
随着你的键盘的主视图的加载,Xcode的调试器连接到你的键盘并且激活断点。

为你的自定义键盘配置Info.plist文件

Info.plist文件让你静态的声明自定义键盘的一些特性,包过主要语言、是否要求开启完全访问。
进入工程找到你的自定义键盘target,在工程导航中选择Info.plist文件来检查这些属性。
在文本格式的显示中,键盘的属性定义如下:

<key>NSExtension</key>
<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>IsASCIICapable</key>
        <false/>
        <key>PrefersRightToLeft</key>
        <false/>
        <key>PrimaryLanguage</key>
        <string>en-US</string>
        <key>RequestsOpenAccess</key>
        <false/>
    </dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.keyboard-service</string>
    <key>NSExtensionPrincipalClass</key>
    <string>KeyboardViewController</string>
</dict>

这些属性对应的key都能够在App Extension Keys中找到。使用NSExtensionAttributes自定的关键字来描述这些功能和需求:
IsASCIICapable--布尔值,默认是NO。描述了键盘能否在文档中插入ASCII字符。如果你的键盘开启了UIKeyboardTypeASCIICapable这个功能,请把它设置为YES
PrefersRightToLeft--布尔值,默认是NO。设置你的键盘支持从右至左的输入方式的语言。
PrimaryLanguage--string类型的值,默认是en-US。使用<language>-<REGION>的格式来描述键盘使用的主要语言。你可以在 http://www.opensource.apple.com/source/CF/CF-476.14/CFLocaleIdentifier.c中找到正确的语言和区域描述。
RequestsOpenAccess--布尔值,默认是NO。描述了键盘是否能够扩展它的沙盒(和主App通讯的共享缓存空间)。开启共享缓存你的键盘能够获得更多的功能:

  • 访问定位服务、通讯录数据、相机等,每一项第一次访问都需要用户统一。
  • 和容器App共享数据缓存,能够做到比如在容器App中管理自定义词典UI这样的功能。
  • 能够通过网络发送键盘输入数据或者其他输入事件和数据以进行服务器端处理。
  • 能够使用 [UIPasteboard](https://developer.apple.com/documentation/uikit/uipasteboard)
  • 通过playInputClick方法来播放击键声音。
  • 访问icloud,根据用户来保存键盘相关的设置,自定义校正词典等。
  • 访问Game Center和通过容器App使用内购。
  • 支持移动设备管理(MDM),与被管理的apps协同工作。
    使用完全访问功能时,请详细阅读 Designing for User Trust中的注意事项,确保对用户数据的尊重和保护。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容