View Programming Guide for iOS

View Programming Guide for iOS 阅读笔记

(个人学习记录,从文档中摘抄出部分,谷歌翻译+手改,语句略生硬,错误之处欢迎指正)

(一) View and Window Architecture

View Architecture Fundamentals

视图与Core Animation图层一起工作来处理视图内容的渲染和动画。 UIKit中的每个视图都由layer对象(通常是CALayer类的实例)支持,该对象管理视图的后备存储并处理与视图相关的动画。你执行的大多数操作应该通过UIView接口。但是,在需要更多地控制视图的渲染或动画行为的情况下,您可以通过其layer执行操作。

Views work in conjunction with Core Animation layers to handle the rendering and animating of a view’s content. Every view in UIKit is backed by a layer object (usually an instance of the CALayer class), which manages the backing store for the view and handles view-related animations. Most operations you perform should be through the UIView interface. However, in situations where you need more control over the rendering or animation behavior of your view, you can perform operations through its layer instead.

bar button item按钮本身不是视图(不是继承于UIView),但它内部管理着一个视图。

bar button item (which is not a view itself but which manages a view internally)

每个视图都有一个相应的图层对象,可以从该view的layer属性中进行访问。 (因为bar button不是view,所以不能直接访问它的layer。)这些layer对象的背后是Core Animation渲染对象,最终是由硬件中的缓冲区来管理屏幕上的实际显示位。

Every view has a corresponding layer object that can be accessed from that view’s layer property. (Because a bar button item is not a view, you cannot access its layer directly.) Behind those layer objects are Core Animation rendering objects and ultimately the hardware buffers used to manage the actual bits on the screen.

Core Animation layer 对象的使用对性能有重要影响。视图对象的实际绘图代码尽可能少地调用,并且在调用代码时,结果由Core Animation缓存并在稍后重用。重用已经呈现的内容消除了更新视图通常需要的昂贵的绘图周期。在内容可以被操纵的动画中,重用这些内容尤为重要。这种重复使用比创建新内容要便宜得多。

The use of Core Animation layer objects has important implications for performance. The actual drawing code of a view object is called as little as possible, and when the code is called, the results are cached by Core Animation and reused as much as possible later. Reusing already-rendered content eliminates the expensive drawing cycle usually needed to update views. Reuse of this content is especially important during animations, where the existing content can be manipulated. Such reuse is much less expensive than creating new content.

View Hierarchies and Subview Management

每个父视图都将其子视图存储在有序数组中,并且该数组中的顺序也会影响每个子视图的可见性。如果两个兄弟子视图彼此重叠,则最后添加的子视图(或移动到子视图数组的末尾)会呈现在另一个之上。

Each superview stores its subviews in an ordered array and the order in that array also affects the visibility of each subview. If two sibling subviews overlap each other, the one that was added last (or was moved to the end of the subview array) appears on top of the other.

父视图 - 子视图关系也会影响多个视图行为。更改父视图的大小会产生连锁效应,从而导致任何子视图的大小和位置也发生变化。当您更改父视图的大小时,可以通过适当地配置视图来控制每个子视图的调整大小行为。影响子视图的其他更改包括隐藏父视图、更改父视图的alpha(透明度)或将数学变换应用于父视图的坐标系。

The superview-subview relationship also impacts several view behaviors. Changing the size of a parent view has a ripple effect that can cause the size and position of any subviews to change too. When you change the size of a parent view, you can control the resizing behavior of each subview by configuring the view appropriately. Other changes that affect subviews include hiding a superview, changing a superview’s alpha (transparency), or applying a mathematical transform to a superview’s coordinate system.

视图层次结构中的视图排列也决定了您的应用程序如何响应事件。当在特定视图内发生触摸时,系统将带有触摸信息的事件对象直接发送到该视图进行处理。但是,如果视图不处理特定的触摸事件,它可以将事件对象传递给它的父视图。如果父视图不处理事件,它将事件对象传递给它的父视图,以此类推到响应者链。特定视图也可以将事件对象传递给干预响应者对象,如视图控制器。如果没有对象处理事件,它最终会到达应用程序对象,通常会丢弃它。

The arrangement of views in a view hierarchy also determines how your application responds to events. When a touch occurs inside a specific view, the system sends an event object with the touch information directly to that view for handling. However, if the view does not handle a particular touch event, it can pass the event object along to its superview. If the superview does not handle the event, it passes the event object to its superview, and so on up the responder chain. Specific views can also pass the event object to an intervening responder object, such as a view controller. If no object handles the event, it eventually reaches the application object, which generally discards it.

The View Drawing Cycle

UIView类使用按需绘制模型来呈现内容。当视图第一次出现在屏幕上时,系统会要求它绘制其内容。系统捕获此内容的快照并将该快照用作视图的视觉呈现。如果你永远不改变视图的内容,视图的绘图代码可能永远不会再被调用。大多数涉及视图的操作都会重用快照图像。如果您更改内容,则会通知系统视图已更改。该视图然后重复绘制视图并捕获新结果的快照的过程。

The UIView class uses an on-demand drawing model for presenting content. When a view first appears on the screen, the system asks it to draw its content. The system captures a snapshot of this content and uses that snapshot as the view’s visual representation. If you never change the view’s content, the view’s drawing code may never be called again. The snapshot image is reused for most operations involving the view. If you do change the content, you notify the system that the view has changed. The view then repeats the process of drawing the view and capturing a snapshot of the new results.

当视图的内容发生变化时,您不会直接重绘这些更改。而是使用setNeedsDisplay或setNeedsDisplayInRect:方法使视图无效。这些方法告诉系统,视图的内容发生了变化,需要在下一次机会中重新绘制。在启动任何绘图操作之前,系统会等待当前运行循环的结束。这种延迟使您有机会使多个视图失效,从您的层次结构中添加或删除视图,隐藏视图,调整视图大小以及一次重新定位视图。然后,您所做的所有更改都会同时反映出来。

When the contents of your view change, you do not redraw those changes directly. Instead, you invalidate the view using either the setNeedsDisplay or setNeedsDisplayInRect: method. These methods tell the system that the contents of the view changed and need to be redrawn at the next opportunity. The system waits until the end of the current run loop before initiating any drawing operations. This delay gives you a chance to invalidate multiple views, add or remove views from your hierarchy, hide views, resize views, and reposition views all at once. All of the changes you make are then reflected at the same time.

注:更改视图的几何图形不会自动导致系统重新绘制视图的内容。视图的contentMode属性决定了如何解释对视图几何体的更改。大多数内容模式在视图的边界内拉伸或重新定位现有快照,并且不会创建新的快照。

Note: Changing a view’s geometry does not automatically cause the system to redraw the view’s content. The view’s contentMode property determines how changes to the view’s geometry are interpreted. Most content modes stretch or reposition the existing snapshot within the view’s boundaries and do not create a new one.

当需要渲染视图的内容时,实际绘图过程会根据视图及其配置而变化。系统视图通常实现私有绘图方法来呈现其内容。系统视图通常会公开可用于配置视图实际外观的接口。对于自定义UIView子类,通常会重写视图的drawRect:方法,并使用该方法绘制视图的内容。还有其他方法可以提供视图的内容,例如直接设置底层的内容,但重写drawRect:方法是最常用的技术。

When the time comes to render your view’s content, the actual drawing process varies depending on the view and its configuration. System views typically implement private drawing methods to render their content. Those same system views often expose interfaces that you can use to configure the view’s actual appearance. For custom UIView subclasses, you typically override the drawRect: method of your view and use that method to draw your view’s content. There are also other ways to provide a view’s content, such as setting the contents of the underlying layer directly, but overriding the drawRect: method is the most common technique.

Content Modes

每个视图都有一个内容模式,用于控制视图如何重复利用其内容以响应视图几何体的更改以及是否回收其内容。首次显示视图时,它会像往常一样渲染其内容,并将结果捕获到底层位图中。之后,对视图几何体的更改并不总是会导致重新创建位图。相反,contentMode属性中的值决定是否缩放位图以适应新的边界,或者只是固定到视图的一个角或边缘。

Each view has a content mode that controls how the view recycles its content in response to changes in the view’s geometry and whether it recycles its content at all. When a view is first displayed, it renders its content as usual and the results are captured in an underlying bitmap. After that, changes to the view’s geometry do not always cause the bitmap to be recreated. Instead, the value in the contentMode property determines whether the bitmap should be scaled to fit the new bounds or simply pinned to one corner or edge of the view.

内容模式适合重复利用视图的内容,但您也可以将内容模式设置为UIViewContentModeRedraw值自定义视图在缩放和调整大小操作期间重绘自己。将视图的内容模式设置为该值会迫使系统根据几何变化调用视图的drawRect:方法。一般而言,您应该尽可能避免使用此值,并且您绝对不应该将其用于标准系统视图。

Content modes are good for recycling the contents of your view, but you can also set the content mode to the UIViewContentModeRedraw value when you specifically want your custom views to redraw themselves during scaling and resizing operations. Setting your view’s content mode to this value forces the system to call your view’s drawRect:method in response to geometry changes. In general, you should avoid using this value whenever possible, and you should certainly not use it with the standard system views.

Stretchable Views

注:文档中的方法contentStretch已被弃用了,改用resizableImageWithCapInsets:

只有内容模式为UIViewContentModeScaleToFill,UIViewContentModeScaleAspectFit和UIViewContentModeScaleAspectFill 时才支持可伸缩视图。如果您指定将内容固定到边或角的内容模式(因此实际上并不缩放内容),视图将忽略可拉伸区域。

This means that stretchable views are supported only with the UIViewContentModeScaleToFill, UIViewContentModeScaleAspectFit, and UIViewContentModeScaleAspectFill content modes. If you specify a content mode that pins the content to an edge or corner (and thus does not actually scale the content), the view ignores the stretchable area.

The Relationship of the Frame, Bounds, and Center Properties

主要使用center和frame属性来操作当前视图的几何图形。
其中center属性始终有效,即使缩放或旋转因子已添加到视图的transform中。而frame属性却不是如此,如果视图的transform不等于标识identity transform,则该frame属性被认为是无效的。

You use the center and frame properties primarily for manipulating the geometry of the current view.
The value in the center property is always valid, even if scaling or rotation factors have been added to the view’s transform. The same is not true for the value in the frame property, which is considered invalid if the view’s transform is not equal to the identity transform.

虽然可以独立更改frame,bounds和center属性,但通过以下方式对一个属性的更改会影响其他属性:

  • 当您设置frame属性时,bounds属性中的大小值将更改为与frame矩形的新大小相匹配。center属性中的值同样会更改为匹配frame矩形的新中心点。
  • 当您设置center属性时,frame中的原点值会相应更改。
  • 当您设置bounds属性的大小时,frame属性中的大小值将更改为与bounds矩形的新大小相匹配。

Although you can change the frame, bounds, and center properties independent of the others, changes to one property affect the others in the following ways:

  • When you set the frame property, the size value in the bounds property changes to match the new size of the frame rectangle. The value in the center property similarly changes to match the new center point of the frame rectangle.
  • When you set the center property, the origin value in the frame changes accordingly.
  • When you set the size of the bounds property, the size value in the frame property changes to match the new size of the bounds rectangle.

默认情况下,视图的frame不会剪切到其父视图的frame。因此,任何位于他们的父视图frame之外的子视图都将全部呈现。但是,您可以通过将父视图的clipsToBounds属性设置为YES来更改此行为。无论子视图是否可视化剪切,触摸事件只在父视图的边界矩形内才响应。换句话说,如果触摸事件发生在父视图边界矩形之外,不会传递到该视图。

By default, a view’s frame is not clipped to its superview’s frame. Thus, any subviews that lie outside of their superview’s frame are rendered in their entirety. You can change this behavior, though, by setting the superview’s clipsToBounds property to YES. Regardless of whether or not subviews are clipped visually, touch events always respect the bounds rectangle of the target view’s superview. In other words, touch events occurring in a part of a view that lies outside of its superview’s bounds rectangle are not delivered to that view.

Coordinate System Transformations

  1. 要修改整个视图,请在视图的transform属性中修改affine transform。

To modify your entire view, modify the affine transform in the transform property of your view.

  1. 要修改特定的内容片段,在drawRect:方法中,修改与图形上下文关联的仿射变换。

To modify specific pieces of content in your view’s drawRect: method, modify the affine transform associated with the active graphics context.

当您想要实现动画时,通常会修改视图的transform属性。例如,您可以使用此属性来创建围绕其中心点旋转的视图动画。您不应该使用transform属性对视图进行永久更改(例如在其父视图的坐标空间中修改其视图的位置或大小),对于这种永久性的更改,您应该修改视图的frame。

You typically modify the transform property of a view when you want to implement animations. For example, you could use this property to create an animation of your view rotating around its center point. You would not use this property to make permanent changes to your view, such as modifying its position or size a view within its superview’s coordinate space. For that type of change, you should modify the frame rectangle of your view instead.

注意:修改视图的transform属性时,所有变换均相对于视图的中心点执行。

Note: When modifying the transform property of your view, all transformations are performed relative to the center point of the view.

每个子视图的坐标系建立在其祖先的坐标系上。因此,当您修改视图的transform属性时,该更改会影响视图及其所有子视图。但是,这些更改仅影响屏幕上视图的最终呈现。由于每个视图都会绘制其内容,并将其子视图相对于其边界进行布局,因此它可以在绘制和布局期间忽略其父视图的变换。

The coordinate system of each subview builds upon the coordinate systems of its ancestors. So when you modify a view’s transform property, that change affects the view and all of its subviews. However, these changes affect only the final rendering of the views on the screen. Because each view draws its content and lays out its subviews relative to its own bounds, it can ignore its superview’s transform during drawing and layout.

重要提示:如果视图的transform属性不是identity transform,那么该视图的frame属性值是未定义的,frame一定会被忽略。将transform应用于视图时,必须使用视图的bounds和center属性来获取视图的大小和位置。任何子视图的frame仍然有效,因为它们相对于视图的边界。

Important: If a view’s transform property is not the identity transform, the value of that view’s frame property is undefined and must be ignored. When applying transforms to a view, you must use the view’s bounds and center properties to get the size and position of the view. The frame rectangles of any subviews are still valid because they are relative to the view’s bounds.

Points Versus Pixels

...

The Runtime Interaction Model for Views

  1. 用户触摸屏幕。
  2. 硬件将触摸事件报告给UIKit框架。
  3. UIKit框架将触摸事件打包到 UIEvent 对象中,并将其分派到适当的视图。
  4. 由视图的事件处理代码 来响应事件。
  5. 如果视图的几何因任何原因而更改,UIKit将根据以下规则更新其子视图...
  6. 如果任何视图的任何部分被标记为需要重绘,UIKit会要求视图重绘本身。
  7. 任何更新的视图都与应用程序的其余可见内容合成并发送到图形硬件以供显示。
  8. 图形硬件将渲染的内容传送到屏幕。
  1. The user touches the screen.
  2. The hardware reports the touch event to the UIKit framework.
  3. The UIKit framework packages the touch into a UIEvent object and dispatches it to the appropriate view.
  4. The event-handling code of your view responds to the event.
  5. If the geometry of a view changed for any reason, UIKit updates its subviews according to the following rules...
  6. If any part of any view was marked as needing to be redrawn, UIKit asks the view to redraw itself.
  7. Any updated views are composited with the rest of the application’s visible content and sent to the graphics hardware for display.
  8. The graphics hardware transfers the rendered content to the screen.

如果视图实现了layoutSubviews方法,UIKit会调用它。
您可以在自定义视图中重写此方法,并使用它来调整任何子视图的位置和大小。 例如,提供大型可滚动区域的视图需要使用多个子视图作为“图块”,而不是创建一个大视图,而该视图无论如何都不太适合内存。 在实现这种方法时,视图会隐藏现在不在屏幕上的任何子视图或重新定位它们并使用它们来绘制新呈现的内容。 作为此过程的一部分,视图的布局代码也可以使任何需要重绘的视图失效。

If the view implements the layoutSubviews method, UIKit calls it.
You can override this method in your custom views and use it to adjust the position and size of any subviews. For example, a view that provides a large scrollable area would need to use several subviews as “tiles” rather than create one large view, which is not likely to fit in memory anyway. In its implementation of this method, the view would hide any subviews that are now offscreen or reposition them and use them to draw newly exposed content. As part of this process, the view’s layout code can also invalidate any views that need to be redrawn.

对于显式定义drawRect:方法的自定义视图,UIKit会调用该方法。 你的这个方法的实现应该尽可能快地重绘视图的指定区域,而不是其他的。 此时不要进行额外的布局更改,也不要对应用程序的数据模型进行其他更改。 此方法的目的是更新视图的可视内容。

For custom views that explicitly define a drawRect: method, UIKit calls that method. Your implementation of this method should redraw the specified area of the view as quickly as possible and nothing else. Do not make additional layout changes at this point and do not make other changes to your application’s data model. The purpose of this method is to update the visual content of your view.

Tips for Using Views Effectively

1. Views Do Not Always Have a Corresponding View Controller

视图控制器的工作是管理视图层次结构,该视图层次结构通常包含多个用于实现一些独立功能的视图。
视图控制器提供了许多重要的行为,例如协调屏幕上的视图显示,协调从屏幕移除这些视图,释放内存以响应低内存警告,以及响应界面方向更改而旋转视图。

The job of a view controller is to manage a view hierarchy, which often consists of more than one view used to implement some self-contained feature.
View controllers provide a lot of important behaviors, such as coordinating the presentation of views on the screen, coordinating the removal of those views from the screen, releasing memory in response to low-memory warnings, and rotating views in response to interface orientation changes.

2. Minimize Custom Drawing

虽然自定义绘图有时是必要的,但它也是你应该尽可能避免的东西。 只有当现有的系统视图类没有提供您需要的外观或功能时,您才应该真正做到任何自定义绘图。 只要您的内容可以与现有视图组合在一起,最好的办法就是将这些视图对象组合到一个自定义视图层次结构中。

Although custom drawing is necessary at times, it is also something you should avoid whenever possible. The only time you should truly do any custom drawing is when the existing system view classes do not provide the appearance or capabilities that you need. Any time your content can be assembled with a combination of existing views, your best bet is to combine those view objects into a custom view hierarchy.

3. Take Advantage of Content Modes

内容模式可以减少重绘视图的时间。 默认情况下,视图使用UIViewContentModeScaleToFill内容模式,该模式缩放视图的现有内容以适合视图的frame。 您可以根据需要更改此模式以不同的方式调整您的内容,但是如果可以的话,您应该避免使用UIViewContentModeRedraw内容模式。 不过,无论设置的是哪种内容模式,您都可以通过调用setNeedsDisplay或setNeedsDisplayInRect:来强制视图重绘其内容。

Content modes minimize the amount of time spent redrawing your views. By default, views use the UIViewContentModeScaleToFill content mode, which scales the view’s existing contents to fit the view’s frame rectangle. You can change this mode as needed to adjust your content differently, but you should avoid using the UIViewContentModeRedraw content mode if you can. Regardless of which content mode is in effect, you can always force your view to redraw its contents by calling setNeedsDisplay or setNeedsDisplayInRect:.

4. Declare Views as Opaque Whenever Possible

UIKit使用每个视图的opaque属性来确定视图是否可以优化合成操作。 将自定义视图的该属性值设置为YES会告诉UIKit它不需要在视图后面呈现任何内容。 较少的渲染可以提高您的绘图代码的性能,并且通常会受到鼓励。 当然,如果将opaque属性设置为YES,则视图必须用完全不透明的内容完全填充其frame。

UIKit uses the opaque property of each view to determine whether the view can optimize compositing operations. Setting the value of this property to YES for a custom view tells UIKit that it does not need to render any content behind your view. Less rendering can lead to increased performance for your drawing code and is generally encouraged. Of course, if you set the opaque property to YES, your view mustfill its bounds rectangle completely with fully opaque content.

5. Adjust Your View’s Drawing Behavior When Scrolling

滚动可以在很短的时间内产生大量的视图更新。 如果您的视图的绘制代码未适当调整,则视图的滚动性能可能会很低。 在开始滚动操作时,可以尝试不要试图确保视图的内容始终处于原始状态,而应考虑更改视图的行为。 例如,您可以暂时降低渲染内容的质量,或在滚动正在进行时更改内容模式。 当滚动停止时,您可以将视图恢复到之前的状态并根据需要更新内容。

Scrolling can incur numerous view updates in a short amount of time. If your view’s drawing code is not tuned appropriately, scrolling performance for your view could be sluggish. Rather than trying to ensure that your view’s content is pristine at all times, consider changing your view’s behavior when a scrolling operation begins. For example, you can reduce the quality of your rendered content temporarily or change the content mode while a scroll is in progress. When scrolling stops, you can then return your view to its previous state and update the contents as needed.

6. Do Not Customize Controls by Embedding Subviews

尽管在技术上可以将子视图添加到标准系统控件 - 从UIControl继承的对象 - 您不应该以这种方式定制它们。 支持自定义的控件通过控件类本身的明确和详细记录的接口来实现。 例如,UIButton类包含用于设置按钮的标题和背景图像的方法。 使用定义的定制点意味着您的代码将始终正常工作。 通过在按钮中嵌入自定义图像视图或标签来限制这些方法,如果按钮的实现发生更改,则可能会导致应用程序现在或未来某个时刻的行为不正确。

Although it is technically possible to add subviews to the standard system controls—objects that inherit from UIControl—you should never customize them in this way. Controls that support customizations do so through explicit and well-documented interfaces in the control class itself. For example, the UIButton class contains methods for setting the title and background images for the button. Using the defined customization points means that your code will always work correctly. Circumventing these methods, by embedding a custom image view or label inside the button, might cause your application to behave incorrectly now or at some point in the future if the button’s implementation changes.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容