ELReader
Requirements
- iOS 8.0+
- Alamofire / SnapKit / Fuzi / SVProgressHUD
- Carthage
- Xcode 8.0+
- Swift 3.0+
Introduction
从小说网站爬取数据,并展示.使用 UIPageViewController
展示数据.使用 Fuzi
解析 XML. 使用了传闭包的方法处理页面跳转,具体请看AppTransfrom.swift
.
我觉得这样处理看起来还是比较整洁的.把所有跳转的逻辑都放到了一个类里面,方便管理
BookModel
首先要有一个存储书籍的类作为 model
import Foundation
import Fuzi
//习惯写了协议方便以后嘛
protocol BaseBookProtocol {
var uri: String {get}
var name: String {get}
var category: String {get}
var author: String? {get}
}
class Book: BaseBookProtocol {
let uri: String
let name: String
//小说分类
let category: String
let author: String?
//当前章节编号
var currentChapterNumber: Int = 0
//章节url和章节名称
var catalogues: [(uri: String, title: String)]?
init(uri: String, name: String, category: String, author: String? = nil) {
self.uri = uri
self.name = name
self.category = category
self.author = author
}
//获取章节的 url
func catalogueURL() -> String {
return "http://www.ybdu.com/xiaoshuo" + self.uri
}
// 根据章节数获取文章内容的 url
func chapterURL(index: Int = -1) -> String {
if index == -1 {
return self.chapterURL(index: currentChapterNumber)
}
guard let catalogues = self.catalogues, index < catalogues.count else { return "" }
let catalogue = catalogues[index]
return "http://m.ybdu.com/xiaoshuo" + self.uri + catalogue.uri
}
// 每一章的标题
func chapterHeader(index: Int = -1) -> String {
if index == -1 {
return self.chapterHeader(index: currentChapterNumber)
}
guard let catalogues = self.catalogues, index >= catalogues.count else { return "" }
let catalogue = catalogues[index]
return catalogue.title
}
//根据 alamofire 的数据解析成 book 类
static func creatBooks(from html: HTMLDocument) -> [Book] {
return html.css(".list p").bookListFilterMap { (element) -> Book in
let category = element[0]!.stringValue
let name = element[1]!.stringValue
let uri = element[1]!.attr("href")!.replacingOccurrences(of: "/xiazai", with: "")
let author = element.firstChild(xpath: "text()")?.stringValue.replacingOccurrences(of: "/", with: "")
return Book(uri: uri, name: name, category: category, author: author)
}
}
}
bookListFilterMap
这个方法是对 Fuzi NodeSet
的拓展.为了看起来整洁,主要是 filter 和 map 的实现
extension NodeSet
{
func bookListFilterMap(_ transform: (XMLElement) -> Book ) -> [Book] {
var result: [Book] = []
for element in self where element[0] != nil && element[1] != nil {
result.append(transform(element))
}
return result
}
}
bookList
解决了 model 开始做数据请求了 获取排行榜的小说,并用 tableView 展示 使用我上次做的 ELSegmentView 来做排行榜的切换.
网络请求使用 alamofire
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
segmentView.titles = ["日排行","周排行","月排行","总排行"]
segmentView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
SVProgressHUD.show()
SVProgressHUD.setDefaultMaskType(.clear)
Alamofire.request(Router.loadDayList).responseHTMLDocument { [weak self] (response) in
SVProgressHUD.dismiss(withDelay: 0.4)
switch response.result {
case .success(let value):
// print(value)
self?.dataSource = Book.creatBooks(from: value)
self?.tableView.reloadData()
case .failure(let error):
print(error)
}
}
}
拓展 Alamofire 的 DataRequest 来处理 HTML 的解析
extension DataRequest {
public static func HTMLResponseSerializer() -> DataResponseSerializer<HTMLDocument> {
return DataResponseSerializer { request, response, data, error in
//catch web error
guard error == nil else {
return .failure(BackendError.network(error: error!))
}
//使用 alamofire 解决 data 的提取
let result = Request.serializeResponseData(response: response, data: data, error: nil)
guard case let .success(validData) = result else {
return .failure(BackendError.dataSerialization(error: result.error! as! AFError))
}
//使用 Fuzi 解析 html
do {
let html = try HTMLDocument(data: validData)
return .success(html)
} catch {
return .failure(BackendError.xmlSerialization(error: error))
}
}
}
//解析 HTML
@discardableResult
func responseHTMLDocument(queue: DispatchQueue? = nil, completionHandler: @escaping (DataResponse<HTMLDocument>) -> Void) -> Self {
return response(queue: queue, responseSerializer: DataRequest.HTMLResponseSerializer(), completionHandler: completionHandler)
}
}