(Swift) iOS Apps with REST APIs(七) -- 分頁加載及下拉刷新

本小節(jié)將帶領(lǐng)大家實現(xiàn)App最常用的兩個功能分頁數(shù)據(jù)加載(滾動加載)及下拉刷新。

重要說明: 這是一個系列教程百宇,非本人原創(chuàng)考廉,而是翻譯國外的一個教程。本人也在學(xué)習(xí)Swift携御,看到這個教程對開發(fā)一個實際的APP非常有幫助芝此,所以翻譯共享給大家。原教程非常長因痛,我會陸續(xù)翻譯并發(fā)布婚苹,歡迎交流與分享。

分頁及滾動加載

GitHub在返回數(shù)據(jù)的時候每次僅發(fā)送有限數(shù)量的數(shù)據(jù)鸵膏。假如膊升,我們向GitHub請求所有公共gist數(shù)據(jù)的時候,GitHub并不會返回全部谭企。而是返回16條左右最新的數(shù)據(jù)給我們廓译。如果,我們需要顯示更多數(shù)據(jù)债查,我們需要再次向GitHub請求非区。

如何獲取下一頁

首先讓我們了解一下GitHub是如何提供分頁數(shù)據(jù)的。在GitHub的開發(fā)文檔中有詳細的關(guān)于分頁的描述盹廷,請參閱Pagination章節(jié)征绸。簡而言之,就是在API端點添加?page=2,然后是?page=3管怠,然后是?page=4...

但淆衷,獲取下一頁正確的方式是查看響應(yīng)數(shù)據(jù)中的報頭,具體來說是連接報頭(link header)渤弛。對于公共gist的請求連接報頭看起來如下:

  <https://api.github.com/gists/public?page=2>; rel="next",
  <https://api.github.com/gists/public?page=100>; rel="last"

因此獲取下一頁的連接為:https://api.github.com/gists/public?page=2祝拯。如果,我們調(diào)用了該鏈接她肯,那么返回的結(jié)果中的連接報頭將變的復(fù)雜佳头,如下:

  <https://api.github.com/gists/public?page=3>; rel="next",
  <https://api.github.com/gists/public?page=100>; rel="last",
  <https://api.github.com/gists/public?page=1>; rel="first",
  <https://api.github.com/gists/public?page=1>; rel="prev"

所以加載更多數(shù)據(jù)我們在這里只需要next的URL,那么接下來讓我們解析它:

private func getNextPageFromHeaders(response: NSHTTPURLResponse?) -> String? { 
  if let linkHeader = response?.allHeaderFields["Link"] as? String {
    /* looks like:
    <https://api.github.com/user/20267/gists?page=2>; rel="next", <https://api.github.com/\
  user/20267/gists?page=6>; rel="last"
    */
    // so split on "," then on ";"
    let components = linkHeader.characters.split {$0 == ","}.map { String($0) } 
    // now we have 2 lines like
    // '<https://api.github.com/user/20267/gists?page=2>; rel="next"'
    // So let's get the URL out of there:
    for item in components {
      // see if it's "next"
      let rangeOfNext = item.rangeOfString("rel=\"next\"", options: []) 
      if rangeOfNext != nil {
        let rangeOfPaddedURL = item.rangeOfString("<(.*)>;",
          options: .RegularExpressionSearch)
        if let range = rangeOfPaddedURL {
          let nextURL = item.substringWithRange(range)
          // strip off the < and >;
          let startIndex = nextURL.startIndex.advancedBy(1) 
          let endIndex = nextURL.endIndex.advancedBy(-2) 
          let urlRange = startIndex..<endIndex
          return nextURL.substringWithRange(urlRange)
        }
      }
    }
  }
  return nil
}

Ok晴氨,這段代碼看起來有點復(fù)雜畜晰。讓我們從頭開始逐步解釋一下。首先瑞筐,我們聲明了一個函數(shù),以NSHTTPURLResponse為參數(shù)腊瑟,并將從報頭中解析出來的下一頁的URL字符串并返回聚假。

private func getNextPageFromHeaders(response: NSHTTPURLResponse?) -> String? {

我們先從請求的返回數(shù)據(jù)中解析出Link報頭:

if let linkHeader = response?.allHeaderFields["Link"] as? String {
  ...
}

該報頭有多個格式為:<URL>;rel="type"的連接信息通過逗號進行組合。因此闰非,我們先將它們拆分成數(shù)組膘格,然后循環(huán)進行處理:

// so split on "," then on ";"    
let components = linkHeader.characters.split{$0 == ","}.map{ String($0) }
for item in components {
  ...
}

在我們循環(huán)進行處理的時候,通過檢測是否含有rel="next"來獲取下一頁的URL:

for item in components {
  // see if it's "next"
  let rangeOfNext = item.rangeOfString("rel=\"next\"", options:[])
  if rangeOfNext != nil {
    // found the component with the next URL
    ...
  }
}

接下來财松,我們需要對component進行進一步解析瘪贱,得到具體下一頁的URL。在這里我們使用正則表達式來匹配我們希望解析出來的字符串辆毡。正則表達式是一種特殊的字符模式菜秦,用來描述如何對字符串進行搜索。舉個例子舶掖,我們的URL被幾個字符所包圍球昨,如:<(.*)>;,這里面的.*就是我們所要解析的URL眨攘。<(.*)>;就是一個正則表達式主慰,描述了如何在字符串找到URL。因此鲫售,我們可以使用這個模式來解析我們的下一頁URL共螺。然后我們再把不屬于URL的<>;字符移除掉:

let rangeOfNext = item.rangeOfString("rel=\"next\"", options:[])
if rangeOfNext != nil {
  let rangeOfPaddedURL = item.rangeOfString("<(.*)>;",
    options: .RegularExpressionSearch)
  if let range = rangeOfPaddedURL {
    let nextURL = item.substringWithRange(range)
    // strip off the < and >;
    let startIndex = nextURL.startIndex.advancedBy(1)
    let endIndex = nextURL.endIndex.advancedBy(-2)
    let urlRange = startIndex..<endIndex
    return nextURL.substringWithRange(urlRange)
  }
}

獲取并追加顯示

現(xiàn)在我們已經(jīng)知道當(dāng)用戶滾動時獲取下一頁數(shù)據(jù)的URL地址。那么應(yīng)該在什么時候進行調(diào)用呢情竹?我們還需要為每個調(diào)用該函數(shù)的都返回這個URL藐不,因此需要把它加到完成處理程序中。我們將擴展完成處理程序的增加一個返回值(即下一頁的URL地址):

func getPublicGists(completionHandler: (Result<[Gist], NSError>, String?) -> Void) { 
  alamofireManager.request(GistRouter.GetPublic())
    .validate()
    .responseArray { (response:Response<[Gist], NSError>) in
      guard response.result.error == nil,
        let gists = response.result.value else {
          print(response.result.error) 
          completionHandler(response.result, nil) 
          return
      }
        
      // need to figure out if this is the last page
      // check the link header, if present
      let next = self.getNextPageFromHeaders(response.response)    
      completionHandler(.Success(gists), next)
    }
}

現(xiàn)在我們需要增加一個能夠加載后面幾頁的gists的功能。該功能要求能夠通過一個給定的URL來加載gist時佳吞。所以拱雏,我們需要在getGistsgetPublicGists中修改代碼來實現(xiàn):

func getGists(urlRequest: URLRequestConvertible, completionHandler: 
  (Result<[Gist], NSError>, String?) -> Void) { 
  alamofireManager.request(urlRequest)
    .validate()
    .responseArray { (response:Response<[Gist], NSError>) in
      guard response.result.error == nil,
        let gists = response.result.value else {
        print(response.result.error) 
        completionHandler(response.result, nil) return
      }

      // need to figure out if this is the last page
      // check the link header, if present
      let next = self.getNextPageFromHeaders(response.response)
      completionHandler(.Success(gists), next)
    }
}
  
func getPublicGists(pageToLoad: String?, completionHandler: 
  (Result<[Gist], NSError>, String?) -> Void) {
  if let urlString = pageToLoad {
    getGists(GistRouter.GetAtPath(urlString), completionHandler: completionHandler) 
  } else {
    getGists(GistRouter.GetPublic(), completionHandler: completionHandler)
  }
}

當(dāng)然,我們還需要修改路由器中的GetAtPath方法使得能夠返回請求的URL:

enum GistRouter: URLRequestConvertible {
  static let baseURLString:String = "https://api.github.com"

  case GetPublic() // GET https://api.github.com/gists/public 
  case GetAtPath(String) // GET at given path

  var URLRequest: NSMutableURLRequest { 
    var method: Alamofire.Method {
      switch self { 
        case .GetPublic:
          return .GET 
        case .GetAtPath:
          return .GET 
      }
    }

    let result: (path: String, parameters: [String: AnyObject]?) = { 
      switch self {
      case .GetPublic:
        return ("/gists/public", nil) 
      case .GetAtPath(let path):
        let URL = NSURL(string: path)
        let relativePath = URL!.relativePath! 
        return (relativePath, nil)
      }
    }()

    let URL = NSURL(string: GistRouter.baseURLString)!
    let URLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(result.path))

    let encoding = Alamofire.ParameterEncoding.JSON
    let (encodedRequest, _) = encoding.encode(URLRequest, parameters: result.parameters)

    encodedRequest.HTTPMethod = method.rawValue

    return encodedRequest 
  }
}

因為這里我們得到的是URL全路徑底扳,所以GetAtPath會變得有點麻煩铸抑。幸好,NSURL可以讓我們獲取相對路徑衷模。另外鹊汛,我們也可以通過修改let URL = ...代碼讓它返回我們傳入的全路徑。

如果你的APP也需要這項功能阱冶,那么檢查你的API看看如何獲取更多數(shù)據(jù)刁憋。然后修改你的API管理器中的相應(yīng)getGistsgetPublicGists方法。作為替代木蹬,你可能需要明確的傳入一個頁碼至耻,或者你已經(jīng)加載對象的數(shù)目,或者已加載的最后對象的ID镊叁。分頁這個功能在不同API之間可能實現(xiàn)的方式不同尘颓,但是你還是可以使用這里的框架。

與表格視圖整合

現(xiàn)在讓我們轉(zhuǎn)回到MasterViewController晦譬。一旦我們得到gists疤苹,我們需要更新視圖顯示,并保存下一頁的URL路徑敛腌。我們還需要把數(shù)據(jù)的加載更改為通過指定的URL地址:

class MasterViewController: UITableViewController {
  ...
  var nextPageURLString: String?
    
  ...
}

我們在調(diào)用loadGists時需要把URL地址傳回給它卧土,即使地址為空:

func loadGists(urlToLoad: String?) {
  GitHubAPIManager.sharedInstance.getPublicGists(urlToLoad) { (result, nextPage) in
    self.nextPageURLString = nextPage
      
    guard result.error == nil else {
      print(result.error)
      // TODO: display error
      return
    }
      
    if let fetchedGists = result.value {
      self.gists = fetchedGists
    }
    self.tableView.reloadData()
  }
}

看出來有什么問題么?那這里呢像樊?

if let fetchedGists = result.value {
  seft.gists = theGists
}

假如我們在獲取第二頁的時候會發(fā)生什么呢尤莺?這段代碼會把新得到的的數(shù)據(jù)替換掉原有的數(shù)據(jù),而不是添加生棍。下面讓我們修正它:

if let fetchedGists = result.value {
  if self.nextPageURLString != nil {
    self.gists += fetchedGists
  } else {
    self.gists = fetchedGists
  }
}

這樣就好了缝裁。

viewDidAppear中我們會傳一個nil,這樣就可以加載第一頁的數(shù)據(jù)了:

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
  
  loadGists(nil)
}

何時加載更多數(shù)據(jù)足绅?

到這里我們已經(jīng)編寫了很多代碼捷绑,但到底應(yīng)該在什么時候來調(diào)用加載更多gists的代碼呢?我們這里設(shè)定當(dāng)用戶向下滾動時氢妈,如果只剩下最后5條數(shù)據(jù)可以用來顯示時跺讯,進行加載更多的gists:

override func tableView(tableView: UITableView, cellForRowAtIndexPath
  indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequenueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
    
  let gist = gists[indexPath.row]
  cell.textLabel!.text = gist.description
  cell.detailTextLabel!.text = gist.ownerLogin
  cell.imageView?.image = nil
    
  // set cell.imageView to display image at gist.ownerAvatarURL
  if let urlString = gist.ownerAvatarURL, url = NSURL(string: urlString) {
    cell.imageView?.pin_setImageFromURL(url, placeholderImage:
      UIImage(named: "placeholder.png"))
  } else {
    cell.imageView?.image = UIImage(named: "placeholder.png")
  }
    
  // See if we need to load more gists
  let rowsToLoadFromBottom = 5
  let rowsLoaded = gists.count
  if let nextPage = nextPageURLString {
    if (!isLoading && (indexPath.row >= (rowsLoaded - rowsToLoadFromBottom))) {
      self.loadGists(nextPage)
    }
  }
  
  return cell
}

就是這里進行加載更多:

let rowsToLoadFromBottom = 5
let rowsLoaded = gists.count
if let nextPage = nextPageURLString {
  if (!isLoading && (indexPath.row >= (rowsLoaded - rowsToLoadFromBottom))) {
    self.loadGists(nextPage)
  }
}

如果我們只剩下最后5條數(shù)據(jù)可以顯示了坚嗜,并且可以加載下一頁數(shù)據(jù),那么我們進行加載(除非我們正在加載,這時候直接略過即可)喝噪。為了加載下一頁數(shù)據(jù),我們只需要調(diào)用loadGists(nextPage),并把下一頁的URL地址傳給它即可。

這里我們需要增加一個isLoading變量觉啊,這樣當(dāng)我們正在加載的時候就不會重復(fù)加載了:

class MasterViewController: UITableViewController {
    
  var detailViewController: DetailViewController? = nil
  var gists = [Gist]()
  var nextPageURLString: String?
  var isLoading = false
  ...
}

如果我們開始加載的時候,把它設(shè)置為true:

func loadGists(urlToLoad: String?) {
  self.isLoading = true
  GitHubAPIManager.sharedInstance.getPublicGists(urlToLoad) { (result, nextPage) in
    self.isLoading = false
    selt.nextPageURLString = nextPage
      
    guard result.error == nil else {
      print(result.error)
      // TODO: display error
      return
    }
      
    if let fetchedGists = result.value {
      if urlToLoad != nil {
        self.gists += fetchedGists
      } else {
        self.gists = fetchedGists
      }
    }
    self.tableView.reloadData()
  }
}

修改你的表格視圖沈贝,當(dāng)用戶滾動到底部的時候可以加載更多數(shù)據(jù)杠人。同時,你還的需要處理下一頁數(shù)據(jù)需要的參數(shù)宋下,如:URL地址嗡善、頁碼、已經(jīng)加載的對象數(shù)目或者已加載最后一個對象的ID等学歧。

分頁及滾動加載小結(jié)

在我們的API調(diào)用中增加滾動加載更多功能的確需要費點勁罩引。保存并運行,當(dāng)你向下滾動時會加載更多數(shù)據(jù)枝笨,而不是初始化時加載的那些:

這里可以下載到本章的代碼袁铐。

下拉刷新

UITableView中增加下拉刷新功能挺起來好像要做很多工作,但實際上不需要横浑。iOS提供的UIRefreshControl控件可以讓我們快速輕松的實現(xiàn)這個特性剔桨。本章我們將增加該功能可以用來刷新gists列表。當(dāng)我們完成時界面看起來如下:

添加下拉刷新

在iOS中UITableViewUIRefreshControl是為彼此進行設(shè)計的伪嫁。事實上,UITableViewController已經(jīng)包含了一個refreshControl的屬性偶垮,只是默認沒有進行初始化而已张咳。因此,我們將創(chuàng)建一個刷新控件似舵,并把它賦值給MasterViewController脚猾。同時,當(dāng)用戶激活它時將砚哗,我們讓它調(diào)用一個名稱為refresh的函數(shù):

override func viewWillAppear(animated: Bool) {
  self.clearSelectionOnViewWillAppear = self.splitViewController!.collapsed
  
  // add refresh control for pull to refresh
  if (self.refreshControl == nil) {
    self.refreshControl = UIRefreshControl()
    self.refreshControl?.addTarget(self, action: "refresh:",
      forControlEvents: UIControlEvents.ValueChanged)
  }
    
  super.viewWillAppear(animated)
}

然后添加refresh方法:

// MARK: -Pull to Refresh
func refresh(sender: AnyObject) {
  nextPageURLString = nil // so it doesn't try to append the results
  loadGists(nil)
}

如果你現(xiàn)在保存并運行龙助,你會發(fā)現(xiàn)的確會干活,但刷新圖標(biāo)卻不會消失≈虢妫現(xiàn)在讓我們來修正它:

func loadGists(urlToLoad: String?) {
  self.isLoading = true
  GitHubAPIManager.sharedInstance.getPublicGists(urlToLoad) { (result, nextPage) in 
    self.isLoading = false
    self.nextPageURLString = nextPage
        
    // tell refresh control it can stop showing up now
    if self.refreshControl != nil && self.refreshControl!.refreshing {
      self.refreshControl?.endRefreshing()
    }
        
    guard result.error == nil else {
      print(result.error)
      // TODO: display error
      return
    }
        
    if let fetchedGists = result.value {
      if urlToLoad != nil {
        self.gists += fetchedGists
      } else {
        self.gists = fetchedGists
      }
    }
    self.tableView.reloadData()
  }
}

保存并運行提鸟。怎么樣,對你做的下拉刷新還滿意么仅淑?

顯示最后刷新時間

當(dāng)你下拉時称勋,如果能夠在下拉控件中顯示最后的刷新時間是非常友好的。因此涯竟,讓我們來完成它赡鲜。這里我們需要一個時間格式化器用來顯示最后刷新時間空厌。但創(chuàng)建一個NSDateFormatter代價是非常昂貴的(重新設(shè)置格式化方式也是一樣),因此我們將僅創(chuàng)建一個银酬,并保存它后面復(fù)用:

class MasterViewController: UITableViewController { 
  var dateFormatter = NSDateFormatter()
  // ...

  override func viewWillAppear(animated: Bool) { 
    self.clearsSelectionOnViewWillAppear = self.splitViewController!.collapsed

    super.viewWillAppear(animated)
    
    // add refresh control for pull to refresh
    if (self.refreshControl == nil) {
      self.refreshControl = UIRefreshControl()
      self.refreshControl?.attributedTitle = NSAttributedString(string: "Pull to refresh") 
      self.refreshControl?.addTarget(self, action: "refresh:",
        forControlEvents: UIControlEvents.ValueChanged)    
      self.dateFormatter.dateStyle = NSDateFormatterStyle.ShortStyle  
      self.dateFormatter.timeStyle = NSDateFormatterStyle.LongStyle
    }
  }
}

然后我們只需要在每次加載完數(shù)據(jù)后把時間設(shè)置到刷新控件的標(biāo)簽上即可:

func loadGists(urlToLoad: String?) {
  self.isLoading = true
  self.nextPageURLString = nextPage   
  GitHubAPIManager.sharedInstance.getPublicGists(urlToLoad) { (result, nextPage) in
    self.isLoading = false
    // tell refresh control it can stop showing up now
    if self.refreshControl != nil && self.refreshControl!.refreshing {
      self.refreshControl?.endRefreshing() 
    }
    guard result.error == nil else { 
      print(result.error) 
      self.nextPageURLString = nil 
      return
    }

    if let fetchedGists = result.value { 
      if urlToLoad != nil {
        self.gists += fetchedGists 
      } else {
        self.gists = fetchedGists 
      }
    }
      
    // update "last updated" title for refresh control
    let now = NSDate()
    let updateString = "Last Updated at " + self.dateFormatter.stringFromDate(now) 
    self.refreshControl?.attributedTitle = NSAttributedString(string: updateString)

    self.tableView.reloadData() 
  }
}

下拉刷新小結(jié)

保存并運行嘲更。你會發(fā)現(xiàn)每次刷新完成后都會更新刷新控件中的最后更新時間:

這里可以下載到本章的代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揩瞪,一起剝皮案震驚了整個濱河市赋朦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壮韭,老刑警劉巖北发,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異喷屋,居然都是意外死亡琳拨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門屯曹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狱庇,“玉大人,你說我怎么就攤上這事恶耽∶苋危” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵偷俭,是天一觀的道長浪讳。 經(jīng)常有香客問我,道長涌萤,這世上最難降的妖魔是什么淹遵? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮负溪,結(jié)果婚禮上透揣,老公的妹妹穿的比我還像新娘。我一直安慰自己川抡,他們只是感情好辐真,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著崖堤,像睡著了一般侍咱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上密幔,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天放坏,我揣著相機與錄音,去河邊找鬼老玛。 笑死淤年,一個胖子當(dāng)著我的面吹牛钧敞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播麸粮,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼溉苛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弄诲?” 一聲冷哼從身側(cè)響起愚战,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎齐遵,沒想到半個月后寂玲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡梗摇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年拓哟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伶授。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡断序,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出糜烹,到底是詐尸還是另有隱情违诗,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布疮蹦,位于F島的核電站诸迟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏愕乎。R本人自食惡果不足惜阵苇,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望妆毕。 院中可真熱鬧慎玖,春花似錦贮尖、人聲如沸笛粘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薪前。三九已至,卻和暖如春关斜,著一層夾襖步出監(jiān)牢的瞬間示括,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工痢畜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垛膝,地道東北人鳍侣。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像吼拥,于是被迫代替她去往敵國和親倚聚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

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

  • 本小節(jié)將會介紹有關(guān)報頭的一些處理方式凿可,并嘗試進行最原始的圖片加載惑折、緩存等功能處理。最后使用PINRemoteIma...
    CD826閱讀 1,094評論 0 2
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫枯跑、插件惨驶、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,059評論 4 62
  • 8月1日 周二 陰 上午女兒有素描課,一大早她舅舅就把她送回來了敛助。才幾天沒見女兒粗卜,還沒進門就先聽到女兒...
    楊喜鈉媽媽閱讀 172評論 0 0
  • We fight. We kill. We betray one another.But we can rebui...
    歐陽去非閱讀 67評論 0 0
  • 喜歡看沙粒一點點從細小的縫隙間休建,從一頭漏向另一頭,稀散的聲音嘩嘩而下评疗,輕輕脆脆的测砂,好像幸福的聲音悄悄地浸入心間,不...
    芃芃女孩閱讀 368評論 0 1