干貨:CoreText教程:制作一個簡單圖文混排的雜志(翻譯向)

學(xué)習(xí)CoreText入門時發(fā)現(xiàn)的文章炫狱,特此翻譯下來施绎,以資他人學(xué)習(xí)之用 原文鏈接 : https://www.raywenderlich.com/153591/core-text-tutorial-ios-making-magazine-app

Core Text是一個可以結(jié)合Core Graphics/Quartz框架的底層文本渲染引擎楞黄,可以提供精細(xì)的布局和格式化控制
iOS7翠储,蘋果發(fā)布了高層級的Text Kit庫筐乳,它可以存儲隘马,列出并顯示具有各種排版特征的文本野宜。盡管Text Kit非常強(qiáng)大扫步,也滿足日常文本排版的需要,但是Core Text可以提供更精細(xì)的控制匈子。例如河胎,如果你需要直接使用Quartz框架,可以使用Core Text旬牲, 如果你需要打造自己獨(dú)有的排版引擎仿粹,Core Text能幫你實(shí)現(xiàn) 對字體與相對位置相關(guān)的特征,進(jìn)行精細(xì)的控制

此教程將使用Core Text從0到1創(chuàng)建一個簡單的雜志應(yīng)用原茅,那么開始吧

熱身

  • 打開Xcode新建一個swift項(xiàng)目吭历,命名為CoreTextMagazine

    新建項(xiàng)目

  • CoreText.framework導(dǎo)入工程

    導(dǎo)入CoreText.framework

  • 添加 Core Text View
    創(chuàng)建CTView.swift, 復(fù)寫 draw(_:)方法

override func draw(_ rect: CGRect) {
        
        // 獲取當(dāng)前上下文
        guard let context = UIGraphicsGetCurrentContext() else {
            return;
        }
        //轉(zhuǎn)換成uikit坐標(biāo)系
        context.textMatrix = .identity
        context.translateBy(x: 0, y: rect.height)
        context.scaleBy(x: 1, y: -1)
        // 繪制區(qū)域路徑
        let path = CGMutablePath.init()
        path.addRect(rect)
        // 初始化富文本
        let attString = NSAttributedString.init(string: "hello word")
        // 創(chuàng)建 CTFramesetter
        let frameSetter = CTFramesetterCreateWithAttributedString(attString as CFAttributedString)
        // 創(chuàng)建 CTFrame
        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attString.length), path, nil)
        // 在指定上下文繪制CTFrame
        CTFrameDraw(frame, context)
}

由于Quartz坐標(biāo)系與UIKit坐標(biāo)系略有不同,所以需要對坐標(biāo)系進(jìn)行一次轉(zhuǎn)換擂橘,否則繪制出來的文本將會是倒置的

context.textMatrix = .identity
context.translateBy(x: 0, y: rect.height)
context.scaleBy(x: 1, y: -1)

項(xiàng)目跑一下, 成功渲染


渲染文本
  • CoreText 對象模型
    CTFramesetter是啥晌区?CTFrame又是啥?祭出此圖
    CoreText對象模型

當(dāng)你創(chuàng)建了一個CTFramesetter,并且為它提供了一個NSAttributedString朗若,將會自動創(chuàng)建一個CTTypesetter來管理字體恼五,接下來就可以使用CTFramesetter創(chuàng)建一個或者多個CTFrame來渲染文本

創(chuàng)建CTFrame的時候,CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attString.length), path, nil)我們給定了范圍和文本的繪制路徑范圍哭懈,Core Text自動為每一行文本創(chuàng)建了一個CTLine灾馒,每一塊文本創(chuàng)建了一個CTRun, 每個CTRun能夠設(shè)置不同的屬性,例如你可以創(chuàng)建一個紅色字體的CTRun遣总,再創(chuàng)建另一個為粗體的CTRun睬罗,這就是為什么說Core Text能對文本進(jìn)行精細(xì)控制的原因了

擼起袖子,開干

請下載素材鏈接:百度云鏈接:https://pan.baidu.com/s/1dF5zVkH 解壓之后導(dǎo)入工程即可

我們需要對不同的文本設(shè)置不同的屬性旭斥,我們需要新建一個文本修飾格式解析器來解析 zombies.txt中的屬性標(biāo)簽來格式化文本

  • 新建MarkupParser.swift 繼承 NSObject
    我們首先可以粗看下zombies.txt中內(nèi)容
zombies.txt

img src標(biāo)簽引用了圖片容达,font color/face標(biāo)簽決定了文本的顏色和字體

MarkupParser.swift鍵入如下代碼

// MARK: - 屬性
var color: UIColor = .black
var fontName: String = "Arial"
var attrString: NSMutableAttributedString!
var images: [[String: Any]] = []
    
// MARK: - 初始化方法
override init() {
    super.init()
}
    
// MARK: - 內(nèi)部方法,解析html文本
func parseMarkup(_ markup: String) {
    
}

類中帶有字體顏色垂券,字體等屬性花盐,parseMarkup(_:)將從文本中解析成屬性文本

對于以下文本

These are <font color="red">red<font color="black"> and
<font color="blue">blue <font color="black">words.

將被解析渲染成如下樣式


  • 解析標(biāo)簽
    將如下代碼填入parseMarkup(_:)方法
//  attrString初始化為空,最終會被賦值最終解析結(jié)果
attrString = NSMutableAttributedString(string: "")
// 解析
do {
       // 匹配標(biāo)簽塊
       let regex = try NSRegularExpression.init(pattern: "(.*?)(<[^>]+>|\\Z)", options: NSRegularExpression.Options.dotMatchesLineSeparators)
       let chunks = regex.matches(in: markup, options: NSRegularExpression.MatchingOptions.init(rawValue: 0), range: NSRange.init(location: 0, length: markup.count))
} catch _ {
}

正則匹配出所有的標(biāo)簽塊


現(xiàn)在所有的匹配結(jié)果都在chunks變量中菇爪,只需要遍歷chunks來創(chuàng)建AttributedString即可

在此之前算芯,我們注意到matches(in:options:range:)接受了一個NSRange類型參數(shù),接下來還會有許多用到NSRange 轉(zhuǎn)換成 Range的地方凳宙,添加如下代碼也祠,可將 NSRange 轉(zhuǎn)換成 Range:

// MARK: - String NSRange 轉(zhuǎn)換成 Range
extension String {
    func range(from range: NSRange) -> Range<String.Index>? {
        guard let from16 = utf16.index(utf16.startIndex,
                                       offsetBy: range.location,
                                       limitedBy: utf16.endIndex),
            let to16 = utf16.index(from16, offsetBy: range.length, limitedBy: utf16.endIndex),
            let from = String.Index(from16, within: self),
            let to = String.Index(to16, within: self) else {
                return nil
        }
        return from ..< to
    }
}

parseMarkup(_:) 繼續(xù)添加如下代碼

// 設(shè)定默認(rèn)字體
let defaultFont: UIFont = .systemFont(ofSize: UIScreen.main.bounds.size.height / 40)
// 遍歷匹配結(jié)果
for chunk in chunks {
    // 獲取當(dāng)前匹配結(jié)果 NSTextCheckingResult 在原文本中的范圍
    guard let markupRange = markup.range(from: chunk.range) else { continue }
    // 以符號 "<" 分割句子
    let parts = markup[markupRange].components(separatedBy: "<")
    // 從 fontName 屬性(Arial)創(chuàng)建字體, 若無該字體,則使用默認(rèn)字體 defaultFont
    let font = UIFont(name: fontName, size: UIScreen.main.bounds.size.height / 40) ?? defaultFont
    // 為 NSAttributedString 創(chuàng)建 字體顏色和字體 屬性
    let attrs = [NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.font: font] as [NSAttributedStringKey : Any]
    // 將屬性 應(yīng)用于 parts[0]
    let text = NSMutableAttributedString(string: parts[0], attributes: attrs)
    attrString.append(text)
}

為了解析處理font標(biāo)簽近速,繼續(xù)添加如下代碼

// 如果分割后的模式數(shù)組長度小于等于1诈嘿,則略過 說明不帶有形如 <> 的匹配
if parts.count <= 1 {
    continue
}
let tag = parts[1]
// 如果 parts[1] ( < 之后的文本,也就是標(biāo)簽名) 是 font
if tag.hasPrefix("font") {
    // 匹配顏色屬性
    let colorRegex = try NSRegularExpression(pattern: "(?<=color=\")\\w+",
                                             options: NSRegularExpression.Options(rawValue: 0))
    colorRegex.enumerateMatches(in: tag,
                                options: NSRegularExpression.MatchingOptions(rawValue: 0),
                                range: NSMakeRange(0, tag.characters.count)) {
            (match, _, _) in
            // 利用 NSObject perform 方法對 color 屬性 賦值獲取到的顏色
            if let match = match,
                let range = tag.range(from: match.range) {
                let colorSel = NSSelectorFromString(tag[range]+"Color")
                color = UIColor.perform(colorSel).takeRetainedValue() as? UIColor ?? .black
            }
    }
    // 正則匹配 face 字體屬性
    let faceRegex = try NSRegularExpression(pattern: "(?<=face=\")[^\"]+",
                                            options: NSRegularExpression.Options(rawValue: 0))
    faceRegex.enumerateMatches(in: tag,
                               options: NSRegularExpression.MatchingOptions(rawValue: 0),
                               range: NSMakeRange(0, tag.characters.count)) { (match, _, _) in
                                
            if let match = match,
                let range = tag.range(from: match.range) {
                fontName = String(tag[range])
            }
    }
} //end of font parsing

到現(xiàn)在為止削葱,已經(jīng)能夠解析出 NSAttributedString

在我們的CTView.swift中添加

// MARK: - Properties
var attrString: NSAttributedString!

// MARK: - Internal
func importAttrString(_ attrString: NSAttributedString) {
  self.attrString = attrString
}

然后 draw(_ rect: CGRect)中刪除
let attrString = NSAttributedString(string: "Hello World") from draw(_:)

ViewController.swift中設(shè)置入口

let ctView = CTView()
ctView.frame = view.frame
view.addSubview(ctView)

guard let file = Bundle.main.path(forResource: "zombies", ofType: "txt") else { return }

do {
    let text = try String(contentsOfFile: file, encoding: .utf8)
    // 解析器解析
    let parser = MarkupParser()
    parser.parseMarkup(text)
    ctView.importAttrString(parser.attrString)

} catch _ {
}

運(yùn)行一下奖亚,Cool, 效果如下

image.png

雜志布局

我們不僅僅滿足一個只顯示單頁面的應(yīng)用,CTFrameGetVisibleStringRange使我們能控制一個frame中能顯示多少文字析砸,你可以創(chuàng)建列昔字,顯示滿了之后,可以再次創(chuàng)建一列
在這個應(yīng)用中首繁,我們將以列為單位作郭,構(gòu)建多個頁面,最終構(gòu)成一個雜志APP

Let us down

我們先將CTView.swift中基類換成UIScrollView, 使App能夠支持多頁滾動

class CTView: UIScrollView {

到目前為止我們在CTView.swift中創(chuàng)建了一個framesetter弦疮,生成了并繪制了一個CTFrame
接下來創(chuàng)建一個新的類CTColumnView.swift繼承于UIView

class CTColumnView: UIView {

    var ctFrame: CTFrame!
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }
    
    required init(frame: CGRect, ctframe: CTFrame) {
        super.init(frame: frame)
        self.ctFrame = ctframe
        backgroundColor = .white
    }
    
    // MARK: -
    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        // 轉(zhuǎn)換成UIKit坐標(biāo)系
        context.textMatrix = .identity
        context.translateBy(x: 0, y: bounds.size.height)
        context.scaleBy(x: 1.0, y: -1.0)
        // 在上下文中繪制 CTFrame
        CTFrameDraw(ctFrame, context)
    }
}

接下來我們需要一個CTSettings.swift來對Column列進(jìn)行配置

class CTSettings {

    // MARK: - 屬性
    let margin: CGFloat = 20 // 邊距
    var columnsPerPage: CGFloat! // 每頁列數(shù)
    var pageRect: CGRect! // 頁面大小
    var columnRect: CGRect! // 列大小
    
    // MARK: - 初始化
    init() {
        // 如果是iphone 每頁顯示1列夹攒,否則每頁兩列
        columnsPerPage = UIDevice.current.userInterfaceIdiom == .phone ? 1 : 2
        // 頁面frame 邊距設(shè)置為 margin大小
        pageRect = UIScreen.main.bounds.insetBy(dx: margin, dy: margin)
        // 設(shè)置列的frame
        columnRect = CGRect(x: 0,
                            y: 0,
                            width: pageRect.width / columnsPerPage,
                            height: pageRect.height).insetBy(dx: margin, dy: margin)
    }
}

打開CTView.swift, 刪去已有代碼, 添加如下代碼

class CTView: UIScrollView {

func buildFrames(withAttrString attrString: NSAttributedString,
                     andImages images: [[String: Any]]) {
        // 允許UIScrollview 翻頁進(jìn)行翻動
        isPagingEnabled = true
        // CTFrameSetter 將創(chuàng)建每列對應(yīng)的 CTFrame
        let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
        // 屬性
        var pageView = UIView()
        var textPos = 0 //當(dāng)前字符所在位置
        var columnIndex: CGFloat = 0 //當(dāng)前列下標(biāo)
        var pageIndex: CGFloat = 0 //當(dāng)前頁面下標(biāo)
        let settings = CTSettings() //配置
        // 循環(huán)遍歷,生成列
        while textPos < attrString.length {

繼續(xù)向遍歷胁塞,生成列的循環(huán)代碼添加:

// columnIndex %s ettings.columnsPerPage為零(truncatingRemainder:對浮點(diǎn)數(shù)取余)咏尝,說明為頁面第一列压语,需要新建一個頁,并設(shè)置frame
    if columnIndex.truncatingRemainder(dividingBy: settings.columnsPerPage) == 0 {
        columnIndex = 0
        pageView = UIView(frame: settings.pageRect.offsetBy(dx: pageIndex * bounds.width, dy: 0))
        addSubview(pageView)
        // 頁面索引自增
        pageIndex += 1
    }
    // 列寬度
    let columnXOrigin = pageView.frame.size.width / settings.columnsPerPage
    // 列偏移量
    let columnOffset = columnIndex * columnXOrigin
    // 計(jì)算列的frame
    let columnFrame = settings.columnRect.offsetBy(dx: columnOffset, dy: 0)
    
    // 創(chuàng)建位置路徑编检,確定text分繪制范圍
    let path = CGMutablePath()
    path.addRect(CGRect(origin: .zero, size: columnFrame.size))
    // 創(chuàng)建 CTFramesetter 用來創(chuàng)建 CTFrame
    let ctframe = CTFramesetterCreateFrame(framesetter, CFRangeMake(textPos, 0), path, nil)
    // 創(chuàng)建列視圖
    let column = CTColumnView(frame: columnFrame, ctframe: ctframe)
    pageView.addSubview(column)
    // 獲取CTFrame 能容納多少文本胎食,從而更新textPos
    let frameRange = CTFrameGetVisibleStringRange(ctframe)
    textPos += frameRange.length
    // 列數(shù)指針自增
    columnIndex += 1
}

上述代碼中,確定和計(jì)算出了每一列視圖的位置和應(yīng)該顯示的文本范圍
最后允懂,代碼末尾厕怜,只需要重新設(shè)定下UIScrollViewcontentSize即可

// 更新UIScrollview的contentSize
contentSize = CGSize(width: CGFloat(pageIndex) * bounds.size.width,
                             height: bounds.size.height)

ViewController中調(diào)用ctView.buildFrames即可

let text = try String(contentsOfFile: file, encoding: .utf8)
let parser = MarkupParser()
parser.parseMarkup(text)
//ctView.importAttrString(parser.attrString)
ctView.buildFrames(withAttrString: parser.attrString, andImages: parser.images)

運(yùn)行一下,wonderful蕾总,一個可翻頁效果的App就有了


預(yù)覽

為App渲染圖片

盡管Core text無法直接繪制圖片酣倾,但是它可以為圖片預(yù)留顯示空間 ,通過CTRun的代理CTRunDelegate谤专,我們可以設(shè)定CTRun的上升下降高度,和它的寬度午绳,模型如下圖所示

CTRunDelegate

每當(dāng)Core Text遇到一個CTRun置侍,它就會詢問代理我需要為這數(shù)據(jù)塊預(yù)留多少空間?拦焚,通過CTRunDelegate蜡坊,我們就能為圖片顯示預(yù)留出空間了

首先在MarkupParser.swift中添加解析img標(biāo)簽的代碼

// image 數(shù)組 添加 圖片屬性字典
images += [["width": NSNumber(value: Float(width)),
            "height": NSNumber(value: Float(height)),
            "filename": filename,
            "location": NSNumber(value: attrString.length)]]
// 定義CTRun屬性結(jié)構(gòu)體
struct RunStruct {
    let ascent: CGFloat
    let descent: CGFloat
    let width: CGFloat
}
// Memory指針 相當(dāng)于RunStruct 結(jié)構(gòu)體指針
let extentBuffer = UnsafeMutablePointer<RunStruct>.allocate(capacity: 1)
extentBuffer.initialize(to: RunStruct(ascent: height, descent: 0, width: width))
//  創(chuàng)建CTRunDelegateCallbacks 控制占位
var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in
}, getAscent: { (pointer) -> CGFloat in
    let d = pointer.assumingMemoryBound(to: RunStruct.self)
    return d.pointee.ascent
}, getDescent: { (pointer) -> CGFloat in
    let d = pointer.assumingMemoryBound(to: RunStruct.self)
    return d.pointee.descent
}, getWidth: { (pointer) -> CGFloat in
    let d = pointer.assumingMemoryBound(to: RunStruct.self)
    return d.pointee.width
})
// 創(chuàng)建綁定了回調(diào)的代理
let delegate = CTRunDelegateCreate(&callbacks, extentBuffer)
// 將代理封裝至屬性字典
let attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedStringKey): (delegate as Any)]
attrString.append(NSAttributedString(string: " ", attributes: attrDictionaryDelegate))
}

現(xiàn)在MarkupParser可以解析處理img標(biāo)簽了,現(xiàn)在我們只需讓CTColumnView && CTView繪制出來就行了

對于CTColumnView.swift添加屬性

var images: [(image: UIImage, frame: CGRect)] = []

并在draw(_ rect: CGRect)中添加繪制圖片代碼

// 繪制圖片
for imageData in images {
    if let image = imageData.image.cgImage {
        let imgBounds = imageData.frame
        context.draw(image, in: imgBounds)
    }
}

CTView.swift中添加屬性

var imageIndex: Int!

并且在buildFrames(withAttrString:andImages:):方法中做初始化

imageIndex = 0

再次添加attachImagesWithFrame(_:ctframe:margin:columnView)方法

func attachImagesWithFrame(_ images: [[String: Any]],
                           ctframe: CTFrame,
                           margin: CGFloat,
                           columnView: CTColumnView) {
    // 獲取ctframe 的`CTLine`數(shù)組
    let lines = CTFrameGetLines(ctframe) as NSArray
    // 使用CTFrameGetLineOrigins 將ctframe中的行origin 復(fù)制到數(shù)組 origins
    var origins = [CGPoint](repeating: .zero, count: lines.count)
    // CFRangeMake(0, 0)代表轉(zhuǎn)換整個CTFrame
    CTFrameGetLineOrigins(ctframe, CFRangeMake(0, 0), &origins)
    // 獲取圖片對象的location屬性赎败,如果沒有值直接返回
    var nextImage = images[imageIndex]
    guard var imgLocation = nextImage["location"] as? Int else {
        return
    }
    // 遍歷CTLine
    for lineIndex in 0..<lines.count {
    }
}

繼續(xù)在循環(huán)中添加代碼

// 遍歷CTLine
for lineIndex in 0..<lines.count {
    let line = lines[lineIndex] as! CTLine
    // 如果CTRun, 文件名秕衙,圖片都存在
    if let glyphRuns = CTLineGetGlyphRuns(line) as? [CTRun],
        let imageFilename = nextImage["filename"] as? String,
        let img = UIImage(named: imageFilename)  {
        for run in glyphRuns {
            // 如果當(dāng)前CTRun的范圍range沒有包含nextImage,直接進(jìn)入一下循環(huán)
            let runRange = CTRunGetStringRange(run)
            if runRange.location > imgLocation || runRange.location + runRange.length <= imgLocation {
                continue
            }
            // 通過 CTRunGetTypographicBounds 計(jì)算圖片的大小
            var imgBounds: CGRect = .zero
            var ascent: CGFloat = 0
            imgBounds.size.width = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, nil, nil))
            imgBounds.size.height = ascent
            // 通過 CTLineGetOffsetForStringIndex 計(jì)算 CTLine x軸的偏移量僵刮,
            let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil)
            // 偏移量需要加上 imgBounds 的 origin
            imgBounds.origin.x = origins[lineIndex].x + xOffset
            imgBounds.origin.y = origins[lineIndex].y
            // 將image 和 image繪制的位置 加入 columnView
            columnView.images += [(image: img, frame: imgBounds)]
            // 圖片下標(biāo)自增,更新imgLocation
            imageIndex! += 1
            if imageIndex < images.count {
                nextImage = images[imageIndex]
                imgLocation = (nextImage["location"] as AnyObject).intValue
            }
        }
    }
}

最終,在buildFrames(withAttrString:andImages:)方法中,語句pageView.addSubview(column)之前調(diào)用即可

if images.count > imageIndex {
  attachImagesWithFrame(images, ctframe: ctframe, margin: settings.margin, columnView: column)
}

大功告成


APP

github完整源碼地址(注釋完備) 傳送門:https://github.com/madaoCN/CoreTextMagzine 給個start唄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汉规,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌近忙,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件攘残,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門寄疏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蹈胡,你說我怎么就攤上這事驯妄◆嫖保” “怎么了侠仇?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我磅甩,道長僧叉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任郎笆,我火速辦了婚禮谭梗,結(jié)果婚禮上激捏,老公的妹妹穿的比我還像新娘序六。我一直安慰自己爆土,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布诸蚕。 她就那樣靜靜地躺著坏瘩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪漠魏。 梳的紋絲不亂的頭發(fā)上倔矾,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音柱锹,去河邊找鬼哪自。 笑死,一個胖子當(dāng)著我的面吹牛禁熏,可吹牛的內(nèi)容都是我干的壤巷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞧毙,長吁一口氣:“原來是場噩夢啊……” “哼胧华!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宙彪,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤撑柔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后您访,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铅忿,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年灵汪,在試婚紗的時候發(fā)現(xiàn)自己被綠了檀训。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柑潦。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖峻凫,靈堂內(nèi)的尸體忽然破棺而出渗鬼,到底是詐尸還是另有隱情,我是刑警寧澤荧琼,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布譬胎,位于F島的核電站,受9級特大地震影響命锄,放射性物質(zhì)發(fā)生泄漏堰乔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一脐恩、第九天 我趴在偏房一處隱蔽的房頂上張望镐侯。 院中可真熱鬧,春花似錦驶冒、人聲如沸苟翻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崇猫。三九已至,卻和暖如春需忿,著一層夾襖步出監(jiān)牢的瞬間诅炉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工贴谎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留汞扎,地道東北人季稳。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓擅这,卻偏偏與公主長得像,于是被迫代替她去往敵國和親景鼠。 傳聞我的和親對象是個殘疾皇子仲翎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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

  • 原文鏈接:https://www.raywenderlich.com/153591/core-text-tutor...
    VernonVan閱讀 2,800評論 0 23
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件铛漓、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評論 4 62
  • 時間過得真快浓恶,一轉(zhuǎn)眼已畢業(yè)三年玫坛。畢業(yè)的第一年,我懷孕結(jié)婚了包晰。那時候什么都不懂湿镀,只覺得有愛情很美好炕吸,完全的智商為負(fù),...
    田荷姑娘閱讀 212評論 0 0
  • 在“電商”這個詞還沒有出現(xiàn)以前勉痴,世界各國把各種產(chǎn)業(yè)劃分為三大類:第一產(chǎn)業(yè)赫模、第二產(chǎn)業(yè)和第三產(chǎn)業(yè)。 小時候蒸矛,總是在新聞...
    岳量閱讀 747評論 0 1
  • 換新電腦了瀑罗,今天開更了 一直在印象筆記上放著,發(fā)霉了都雏掠,還被人說不知道分享斩祭,委屈啊,哈哈磁玉,以后就住在簡書上了 Co...
    歡歡1206閱讀 380評論 8 6