Swift 项目总结 02 - 常用分类方法

PS:Xcode 版本是 Version 9.2 (9C40b),编译 Swift 版本是:3.2

NSObject+ClassName

功能:获取某个对象或者某个类的类名字符串(比如 xib 加载)

extension NSObject {
    
    /// 返回类名字符串
    static var className: String {
        return String(describing: self)
    }
    
    /// 返回类名字符串
    var className: String {
        return String(describing: type(of: self))
    }
}

String+BoundingRect

功能:计算字符串在 label 上的宽高(比如 cell 自适应高度)

extension String {

    /// 给定最大宽计算高度,传入字体、行距、对齐方式(便捷调用)
    func heightForLabel(width: CGFloat, font: UIFont, lineSpacing: CGFloat = 5, alignment: NSTextAlignment = .left) -> CGFloat {
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = lineSpacing
        paragraphStyle.alignment = alignment
        let attributes: [String : Any] = [
            NSFontAttributeName: font,
            NSParagraphStyleAttributeName: paragraphStyle
        ]
        let textSize = textSizeForLabel(width: width, height: CGFloat(Float.greatestFiniteMagnitude), attributes: attributes)
        return textSize.height
    }
    
    /// 给定最大宽计算高度,传入属性字典(便捷调用)
    func heightForLabel(width: CGFloat, attributes: [String: Any]) -> CGFloat {
        let textSize = textSizeForLabel(width: width, height: CGFloat(Float.greatestFiniteMagnitude), attributes: attributes)
        return textSize.height
    }
    
    /// 给定最大高计算宽度,传入字体(便捷调用)
    func widthForLabel(height: CGFloat, font: UIFont) -> CGFloat {
        let labelTextAttributes = [NSFontAttributeName: font]
        let textSize = textSizeForLabel(width: CGFloat(Float.greatestFiniteMagnitude), height: height, attributes: labelTextAttributes)
        return textSize.width
    }
    
    /// 给定最大高计算宽度,传入属性字典(便捷调用)
    func widthForLabel(height: CGFloat, attributes: [String: Any]) -> CGFloat {
        let textSize = textSizeForLabel(width: CGFloat(Float.greatestFiniteMagnitude), height: height, attributes: attributes)
        return textSize.width
    }
    
    /// 给定最大宽高计算宽度和高度,传入字体、行距、对齐方式(便捷调用)
    func textSizeForLabel(width: CGFloat, height: CGFloat, font: UIFont, lineSpacing: CGFloat = 5, alignment: NSTextAlignment = .left) -> CGSize {
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = lineSpacing
        paragraphStyle.alignment = alignment
        let attributes: [String : Any] = [
            NSFontAttributeName: font,
            NSParagraphStyleAttributeName: paragraphStyle
        ]
        let textSize = textSizeForLabel(width: width, height: height, attributes: attributes)
        return textSize
    }
    
    /// 给定最大宽高计算宽度和高度,传入属性字典(便捷调用)
    func textSizeForLabel(size: CGSize, attributes: [String: Any]) -> CGSize {
        let textSize = textSizeForLabel(width: size.width, height: size.height, attributes: attributes)
        return textSize
    }
    
    /// 给定最大宽高计算宽度和高度,传入属性字典(核心)
    func textSizeForLabel(width: CGFloat, height: CGFloat, attributes: [String: Any]) -> CGSize {
        let defaultOptions: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
        let maxSize = CGSize(width: width, height: height)
        let rect = self.boundingRect(with: maxSize, options: defaultOptions, attributes: attributes, context: nil)
        let textWidth: CGFloat = CGFloat(Int(rect.width) + 1)
        let textHeight: CGFloat = CGFloat(Int(rect.height) + 1)
        return CGSize(width: textWidth, height: textHeight)
    }
}

extension NSAttributedString {
    
    /// 根据最大宽计算高度(便捷调用)
    func heightForLabel(width: CGFloat) -> CGFloat {
        let textSize = textSizeForLabel(width: width, height: CGFloat(Float.greatestFiniteMagnitude))
        return textSize.height
    }
    
    /// 根据最大高计算宽度(便捷调用)
    func widthForLabel(height: CGFloat) -> CGFloat {
        let textSize = textSizeForLabel(width: CGFloat(Float.greatestFiniteMagnitude), height: height)
        return textSize.width
    }
    
    /// 计算宽度和高度(核心)
    func textSizeForLabel(width: CGFloat, height: CGFloat) -> CGSize {
        let defaultOptions: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
        let maxSize = CGSize(width: width, height: height)
        let rect = self.boundingRect(with: maxSize, options: defaultOptions, context: nil)
        let textWidth: CGFloat = CGFloat(Int(rect.width) + 1)
        let textHeight: CGFloat = CGFloat(Int(rect.height) + 1)
        return CGSize(width: textWidth, height: textHeight)
    }
}

String+RegularExpression

功能:主要是简化和统一外部使用正则表达式

extension String {
    /// 通过正则表达式匹配替换
    func replacingStringOfRegularExpression(pattern: String, template: String) -> String {
        var content = self
        do {
            let range = NSRange(location: 0, length: content.count)
            let expression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
            content = expression.stringByReplacingMatches(in: content, options: .reportCompletion, range: range, withTemplate: template)
        } catch {
            print("regular expression error")
        }
        return content
    }
    
    /// 通过正则表达式匹配返回结果
    func matches(pattern: String) -> [NSTextCheckingResult] {
        do {
            let range = NSRange(location: 0, length: count)
            let expression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
            let matchResults = expression.matches(in: self, options: .reportCompletion, range: range)
            return matchResults
        } catch {
            print("regular expression error")
        }
        return []
    }
    
    /// 通过正则表达式返回第一个匹配结果
    func firstMatch(pattern: String) -> NSTextCheckingResult? {
        do {
            let range = NSRange(location: 0, length: count)
            let expression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
            let match = expression.firstMatch(in: self, options: .reportCompletion, range: range)
            return match
            
        } catch {
            print("regular expression error")
        }
        return nil
    }
}

String+Substr

功能:字符串截取快捷方法,你懂的(⊙o⊙)…

extension String {
    
    ///  寻找在 startString 和 endString 之间的字符串
    func substring(between startString: String, and endString: String?, options: String.CompareOptions = .caseInsensitive) -> String? {
        let range = self.range(of: startString, options: options)
        if let startIndex = range?.upperBound {
            let string = self.substring(from: startIndex)
            if let endString = endString {
                let range = string.range(of: endString, options: options)
                if let startIndex = range?.lowerBound {
                    return string.substring(to: startIndex)
                }
            }
            return string
        }
        return nil
    }
    
    ///  寻找 prefix 字符串,并返回从 prefix 到尾部的字符串
    func substring(prefix: String, options: String.CompareOptions = .caseInsensitive, isContain: Bool = true) -> String? {
        let range = self.range(of: prefix, options: options)
        if let startIndex = range?.upperBound {
            var resultString = self.substring(from: startIndex)
            if isContain {
                resultString = "\(prefix)\(resultString)"
            }
            return resultString
        }
        return nil
    }
    
    ///  寻找 suffix 字符串,并返回从头部到 suffix 位置的字符串
    func substring(suffix: String, options: String.CompareOptions = .caseInsensitive, isContain: Bool = false) -> String? {
        let range = self.range(of: suffix, options: options)
        if let startIndex = range?.lowerBound {
            var resultString = self.substring(to: startIndex)
            if isContain {
                resultString = "\(resultString)\(suffix)"
            }
            return resultString
        }
        return nil
    }
    
    ///  从 N 位置到尾位置的字符串
    func substring(from: IndexDistance) -> String? {
        let index = self.index(self.startIndex, offsetBy: from)
        return self.substring(from: index)
    }
    
    ///  从头位置到 N 位置的字符串
    func substring(to: IndexDistance) -> String? {
        let index = self.index(self.startIndex, offsetBy: to)
        return self.substring(to: index)
    }
    
    /// 以 lower 为起点,偏移 range 得到的字符串
    func substring(_ lower: IndexDistance, _ range: IndexDistance) -> String? {
        let lowerIndex = self.index(self.startIndex, offsetBy: lower)
        let upperIndex = self.index(lowerIndex, offsetBy: range)
        let range = Range(uncheckedBounds: (lowerIndex, upperIndex))
        return self.substring(with: range)
    }
}

UIFont+Convenience

功能:除了简化自定义字体的引入外,这里可以统一管理字体大小,比如对字体大小进行统一屏幕适配

enum FontWeight: String {
    case light = "Light"
    case regular = "Regular"
    case medium = "Medium"
    case semibold = "Semibold"
    case bold = "Bold"
    case heavy = "Heavy"
}

enum FontType: String {
    case PingFangSC = "PingFangSC"
    case SFProText = "SFProText"
}

extension UIFont {
    
    static func heavyFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
        return customFont(type, weight: .heavy, fontSize: fontSize)
    }
    
    static func regularFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
        return customFont(type, weight: .regular, fontSize: fontSize)
    }
    
    static func boldFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
        return customFont(type, weight: .bold, fontSize: fontSize)
    }
    
    static func lightFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
        return customFont(type, weight: .light, fontSize: fontSize)
    }
    
    static func mediumFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
        return customFont(type, weight: .medium, fontSize: fontSize)
    }
    
    static func semiboldFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
        return customFont(type, weight: .semibold, fontSize: fontSize)
    }
    
    /// 自定义字体
    static func customFont(_ type: FontType, weight: FontWeight, fontSize: CGFloat) -> UIFont {
        let realFontSize = fontSize
        if let customFont = UIFont(name: "\(type.rawValue)-\(weight.rawValue)", size: realFontSize) {
            return customFont
        }
        if #available(iOS 8.2, *) {
            var systemWeight: CGFloat = UIFontWeightRegular
            switch weight {
            case .light:
                systemWeight = UIFontWeightLight
            case .regular:
                systemWeight = UIFontWeightRegular
            case .medium:
                systemWeight = UIFontWeightMedium
            case .semibold:
                systemWeight = UIFontWeightSemibold
            case .bold:
                systemWeight = UIFontWeightBold
            case .heavy:
                systemWeight = UIFontWeightHeavy
            }
            return UIFont.systemFont(ofSize: realFontSize, weight: systemWeight)
        } else {
            return UIFont.systemFont(ofSize: realFontSize)
        }
    }
}

UIView+Convenience

功能:一些常用的视图操作,比如 xib 加载、截图、找响应控制器

extension UIView {
    
    /// 从 xib 中加载视图
    func loadViewFromNib(index: Int = 0) -> UIView? {
        let classInstance = type(of: self)
        let nibName = classInstance.className
        let nib = UINib(nibName: nibName, bundle: nil)
        
        if let views = nib.instantiate(withOwner: self, options: nil) as? [UIView] {
            return views.safeIndex(index)
        }
        return nil
    }

    /// 寻找当前视图所在的控制器
    var responderController: UIViewController? {
        var nextReponder: UIResponder? = self.next
        while nextReponder != nil {
            if let viewController = nextReponder as? UIViewController {
                return viewController
            }
            nextReponder = nextReponder?.next
        }
        return nil
    }

    /// 生成视图的截图
    func displayViewToImage() -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
        if let context = UIGraphicsGetCurrentContext() {
            self.layer.render(in: context)
        }
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }

}

CollectionView+Convenience

功能:主要是为了简化 CollectionView 注册和从缓冲池取控件的代码

extension UICollectionView {
    
    /// 批量注册 Cell
    func registerForCells<T: UICollectionReusableView>(_ cellClasses: [T.Type], isNib: Bool = true) {
        cellClasses.forEach { cellClass in
            registerForCell(cellClass, isNib: isNib)
        }
    }
    
    /// 注册 Cell
    func registerForCell<T: UICollectionReusableView>(_ cellClass: T.Type, identifier: String? = nil, isNib: Bool = true) {
        let nibName = cellClass.className
        let cellIdentifier = identifier ?? nibName
        if isNib {
            self.register(UINib(nibName: nibName, bundle: nil), forCellWithReuseIdentifier: cellIdentifier)
        } else {
            self.register(cellClass, forCellWithReuseIdentifier: cellIdentifier)
        }
    }
    
    /// 注册顶部视图
    func registerForHeader<T: UICollectionReusableView>(_ cellClass: T.Type, identifier: String? = nil, isNib: Bool = true) {
        let nibName = cellClass.className
        let headerIdentifier = identifier ?? nibName
        if isNib {
            self.register(UINib(nibName: nibName, bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerIdentifier)
        } else {
            self.register(cellClass, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerIdentifier)
        }
    }
    
    /// 注册底部视图
    func registerForFooter<T: UICollectionReusableView>(_ cellClass: T.Type, identifier: String? = nil, isNib: Bool = true) {
        let nibName = cellClass.className
        let footerIdentifier = identifier ?? nibName
        if isNib {
            self.register(UINib(nibName: nibName, bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: footerIdentifier)
        } else {
            self.register(cellClass, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: footerIdentifier)
        }
    }
    
    /// 从缓存池取出 Cell
    func dequeueCell<T: UICollectionReusableView>(_ cellClass: T.Type, reuseIdentifier: String? = nil, indexPath: IndexPath) -> T {
        let identifier: String = reuseIdentifier ?? cellClass.className
        if let cell = dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? T {
            return cell
        } else {
            return T()
        }
    }
    
    /// 从缓存池取出顶部或者底部实体
    func dequeueSupplementaryView<T: UICollectionReusableView>(_ viewClass: T.Type, kind: String, indexPath: IndexPath) -> T {
        let identifier = viewClass.className
        if let cell = dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: identifier, for: indexPath) as? T {
            return cell
        } else {
            return T()
        }
    }
    
    /// 滑动到第一个 Cell 位置,通过增加判断,防止奔溃
    func scrollToFirstCell(animated: Bool = true) {
        guard self.numberOfSections > 0 else { return }
        guard let count = self.dataSource?.collectionView(self, numberOfItemsInSection: 0) else { return }
        if count > 0 {
            if let flowLayout = self.collectionViewLayout as? UICollectionViewFlowLayout {
                if flowLayout.scrollDirection == .horizontal {
                    scrollToItem(at: IndexPath(row: 0, section: 0), at: .left, animated: animated)
                } else {
                    scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: animated)
                }
            }
        }
    }
}

Array+Convenience

功能:数组便捷操作,其中 safeIndex 是为了防止数组越界,双边遍历的需求是为了优化显示图片列表加载,从用户当前看到的图片开始向两边加载

extension Array {
    
    /// 获取数组中的元素,增加了数组越界的判断
    func safeIndex(_ i: Int) -> Array.Iterator.Element? {
        guard !isEmpty && self.count > abs(i) else {
            return nil
        }
        
        for item in self.enumerated() {
            if item.offset == I {
                return item.element
            }
        }
        return nil
    }
    
    /// 从前面取 N 个数组元素
    func limit(_ limitCount: Int) -> [Array.Iterator.Element] {
        let maxCount = self.count
        var resultCount: Int = limitCount
        if maxCount < limitCount {
            resultCount = maxCount
        }
        if resultCount <= 0 {
            return []
        }
        return self[0..<resultCount].map { $0 }
    }
    
    /// 填充数组数量到 N
    func full(_ fullCount: Int) -> [Array.Iterator.Element] {
        var items = self
        while items.count > 0 && items.count < fullCount {
            items = (items + items).limit(fullCount)
        }
        return items.limit(fullCount)
    }
    
    /// 双边遍历,从中间向两边进行遍历
    func bilateralEnumerated(_ beginIndex: Int, handler: (Int, Array.Iterator.Element) -> Void) {
        let arrayCount: Int = self.count
        var leftIndex: Int = Swift.max(0, Swift.min(beginIndex, arrayCount - 1))
        var rightIndex: Int = leftIndex + 1
        var currentIndex: Int = leftIndex
        var isLeftEnable: Bool = leftIndex >= 0 && leftIndex < arrayCount
        var isRightEnable: Bool = rightIndex >= 0 && rightIndex < arrayCount
        var isLeft: Bool = isLeftEnable ? true : isRightEnable
        while isLeftEnable || isRightEnable {
            currentIndex = isLeft ? leftIndex : rightIndex
            if let element = self.safeIndex(currentIndex) {
                handler(currentIndex, element)
            }
            if isLeft {
                leftIndex -= 1
            } else {
                rightIndex += 1
            }
            isLeftEnable = leftIndex >= 0 && leftIndex < arrayCount
            isRightEnable = rightIndex >= 0 && rightIndex < arrayCount
            if isLeftEnable && !isRightEnable {
                isLeft = true
            } else  if !isLeftEnable && isRightEnable {
                isLeft = false
            } else if isLeftEnable && isRightEnable {
                isLeft = !isLeft
            }
        }
    }
}

NSDictionary+Convenience

功能:简化从字典中取值的代码,并支持多键查找

extension NSDictionary {
    
    // MARK: - 以下都是从字典里取值的快捷方法,支持多键查找和默认返回
    func bool(_ keys: String..., defaultValue: Bool = false) -> Bool {
        return valueForKeys(keys, type: Bool.self) ?? defaultValue
    }
    
    func double(_ keys: String..., defaultValue: Double = 0.0) -> Double {
        return valueForKeys(keys, type: Double.self) ?? defaultValue
    }
    
    func int(_ keys: String..., defaultValue: Int = 0) -> Int {
        return valueForKeys(keys, type: Int.self) ?? defaultValue
    }
    
    func string(_ keys: String..., defaultValue: String? = nil) -> String? {
        return valueForKeys(keys, type: String.self) ?? defaultValue
    }
    
    func dictionary(_ keys: String..., defaultValue: NSDictionary? = nil) -> NSDictionary? {
        return valueForKeys(keys, type: NSDictionary.self) ?? defaultValue
    }
    
    func array<T>(_ keys: String..., type: T.Type, defaultValue: [T] = []) -> [T] {
        return valueForKeys(keys, type: Array<T>.self) ?? defaultValue
    }
    
    // MARK: - 以下是从字典里取值的核心方法,支持多键查找
    fileprivate func valueForKeys<T>(_ keys: [String], type: T.Type) -> T? {
        for key in keys {
            if let result = self[key] as? T {
                return result
            }
        }
        return nil
    }
}

Demo 源代码在这:02-CommonExtensionDemo

这些分类方法都是我在项目中比较常用的,能简化很多代码,希望你们喜欢!O(∩_∩)O哈哈~

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,010评论 4 62
  • 空寂的天空划过一条云砌的线 清风拂过油黑的发丝 缓缓地顺进神经 融入血液流入心扉 那是久久不愈的伤 随着心的跳动日...
    夕馨阅读 247评论 0 1
  • 呵,今天真是忙碌的一天! 几乎所有的工作都是限时完成,于是对名单、打电话、调整、补漏洞,电话讲到嘴软,但嘴软的...
    小鹿says阅读 231评论 0 1
  • 好像很多时候里,田田为我领路。比如这次,休假的日子,起床看到她昨夜发来的推送——厦大简忆。 在她的回忆里,让我仿佛...
    Even_Ge阅读 713评论 12 7
  • 初秋的夜晚,你我邂逅在清水河边!还是那样的皎月,透过梧桐,月光被剪成了碎片! 曾经以为的惊诧,喷薄...
    晞元阅读 465评论 0 0