RxSwift + Moya + ObjectMapper

https://www.jianshu.com/p/173915b943af

use_frameworks!
 
target 'RXDemo' do
    pod 'RxSwift'
    pod 'RxCocoa'
    pod 'Moya-ObjectMapper/RxSwift'
    pod 'Moya/RxSwift'
end
import Moya

let DouBanProvider = MoyaProvider<DouBanAPI>()

public enum DouBanAPI {
    case channels  //获取频道列表
    case playlist(String) //获取歌曲
}

extension DouBanAPI: TargetType {
 
    public var baseURL: URL {
        switch self {
        case .channels:
            return URL(string: "https://www.douban.com")!
        case .playlist(_):
            return URL(string: "https://douban.fm")!
        }
    }
    
    public var path: String {
        switch self {
        case .channels:
            return "/j/app/radio/channels"
        case .playlist(_):
            return "/j/mine/playlist"
        }
    }
    
    public var method: Moya.Method {
        return .get
    }

    public var task: Task {
        switch self {
        case .playlist(let channel):
            var params: [String: Any] = [:]
            params["channel"] = channel
            params["type"] = "n"
            params["from"] = "mainsite"
            return .requestParameters(parameters: params,
                                      encoding: URLEncoding.default)
        default:
            return .requestPlain
        }
    }
    
    public var validate: Bool {
        return false
    }
    
    public var sampleData: Data {
        return "{}".data(using: String.Encoding.utf8)!
    }
    
    public var headers: [String: String]? {
        return nil
    }
}
import UIKit
import ObjectMapper

//豆瓣接口模型
struct Douban: Mappable {
    //频道列表
    var channels: [Channel]?
    
    init?(map: Map) { }
    
    // Mappable
    mutating func mapping(map: Map) {
        channels <- map["channels"]
    }
}

//频道模型
struct Channel: Mappable {
    var name: String?
    var nameEn:String?
    var channelId: String?
    var seqId: Int?
    var abbrEn: String?
    
    init?(map: Map) { }
    
    // Mappable
    mutating func mapping(map: Map) {
        name <- map["name"]
        nameEn <- map["name_en"]
        channelId <- map["channel_id"]
        seqId <- map["seq_id"]
        abbrEn <- map["abbr_en"]
    }
}

//歌曲列表模型
struct Playlist: Mappable {
    var r: Int!
    var isShowQuickStart: Int!
    var song:[Song]!
    
    init?(map: Map) { }
    
    // Mappable
    mutating func mapping(map: Map) {
        r <- map["r"]
        isShowQuickStart <- map["is_show_quick_start"]
        song <- map["song"]
    }
}

//歌曲模型
struct Song: Mappable {
    var title: String!
    var artist: String!
    
    init?(map: Map) { }
    
    // Mappable
    mutating func mapping(map: Map) {
        title <- map["title"]
        artist <- map["artist"]
    }
}


  let data = DouBanProvider.rx.request(.channels)
            .mapObject(Douban.self)
            .map { $0.channels ?? [] }
            .asObservable()
        
        data.bind(to: self.tableView.rx.items) { tableView, _, channel in
            let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier")!
            cell.textLabel?.text = channel.name
            cell.accessoryType = .disclosureIndicator
            return cell
            }.disposed(by: self.disposeBag)
        
        tableView.rx.modelSelected(Channel.self)
            .map{ $0.channelId ?? "" }
            .flatMap { DouBanProvider.rx.request(.playlist($0)) }
            .mapObject(Playlist.self)
            .subscribe(onNext: { [weak self] (playList) in
                if playList.song != nil && playList.song.count > 0 {
                    let artist = playList.song[0].artist!
                    let title = playList.song[0].title!
                    let message = "歌手:\(artist)\n歌曲:\(title)"
                    self?.showAlert(title: "歌曲信息", message: message)
                }
            }).disposed(by: self.disposeBag)
import RxSwift
import RxCocoa
import ObjectMapper

class DoubanService {

    //获取频道数据
    func loadChannels() -> Observable<[Channel]> {
        return DouBanProvider.rx.request(.channels)
            .mapObject(Douban.self)
            .map{ $0.channels ?? [] }
            .asObservable()
    }
    
    //获取歌曲列表数据
    func loadPlaylist(channelId: String) -> Observable<Playlist> {
        return DouBanProvider.rx.request(.playlist(channelId))
            .mapObject(Playlist.self)
            .asObservable()
    }
    
    //获取频道下第一首歌曲
    func loadFirstSong(channelId: String) -> Observable<Song> {
        return loadPlaylist(channelId: channelId)
            .filter{ $0.song != nil && $0.song.count > 0}
            .map{ $0.song[0] }
    }
}
import UIKit
import RxSwift
import RxCocoa

class MusicViewController: UIViewController {
    
    private var viewModel = MusicListViewModel()
    
    private var disposeBag = DisposeBag()
    
    private lazy var tableView: UITableView = {
        let tableView = UITableView(frame: self.view.bounds)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "CellIdentifier")
        tableView.tableFooterView = UIView()
        return tableView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = .white
        
        self.view.addSubview(self.tableView)
        
        let service = DoubanService()
        
        //获取列表数据
        let data = service.loadChannels()
        
        //将数据绑定到表格
        data.bind(to: tableView.rx.items) { (tableView, row, element) in
            let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier")!
            cell.textLabel?.text = "\(element.name!)"
            cell.accessoryType = .disclosureIndicator
            return cell
            }.disposed(by: disposeBag)
        
        //单元格点击
        tableView.rx.modelSelected(Channel.self)
            .map{ $0.channelId ?? "" }
            .flatMap(service.loadFirstSong)
            .subscribe(onNext: {[weak self] song in
                //将歌曲信息弹出显示
                let message = "歌手:\(song.artist!)\n歌曲:\(song.title!)"
                self?.showAlert(title: "歌曲信息", message: message)
            }).disposed(by: disposeBag)
        
    }
    
    //显示消息
    func showAlert(title:String, message:String) {
        let alertController = UIAlertController(title: title,
                                                message: message, preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)
        self.present(alertController, animated: true, completion: nil)
    }
}
import UIKit
import SnapKit

class ViewController: UIViewController {
    
    private lazy var items = [HomeDataItem]()
    
    private lazy var availableTexts = [String]()
    
    private lazy var availableImages = [String]()
    
    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.backgroundColor = .clear
        tableView.dataSource = self
        tableView.delegate = self
        tableView.rowHeight = 50
        tableView.register(HomeTableViewCell.self, forCellReuseIdentifier: "Cell")
        return tableView
    }()
    
    private lazy var deleteButton : UIButton = {
        var deleteButton = UIButton()
        deleteButton.setTitle("Delete", for: .normal)
        deleteButton.setTitle("", for: .disabled)
        deleteButton.backgroundColor = .red
        deleteButton.layer.cornerRadius = 10
        deleteButton.tintColor = .white
        deleteButton.addTarget(self, action: #selector(deleteAction), for: .touchUpInside)
        return deleteButton
    }()
    
    private lazy var addButton : UIButton = {
        var addButton = UIButton()
        addButton.setTitle("Add", for: .normal)
        addButton.setTitle("", for: .disabled)
        addButton.backgroundColor = .blue
        addButton.layer.cornerRadius = 10
        addButton.tintColor = .white
        addButton.addTarget(self, action: #selector(addAction), for: .touchUpInside)
        return addButton
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupUI()
        createDataSources()
    }
    
    private func setupUI() {
        
        let imageView = UIImageView(image: UIImage(named: "bg"))
        imageView.contentMode = .scaleAspectFill
        
        view.addSubview(imageView)
        imageView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        
        view.addSubview(tableView)
        tableView.snp.makeConstraints { make in
            make.left.top.right.equalToSuperview()
            make.bottom.equalToSuperview().offset(self.view.safeAreaInsets.bottom).offset(-100)
        }
        
        view.addSubview(deleteButton)
        deleteButton.snp.makeConstraints { make in
            make.right.equalToSuperview().offset(-20)
            make.top.equalTo(tableView.snp.bottom).offset(10)
            make.size.equalTo(CGSize(width: 100, height: 40))
        }
        
        view.addSubview(addButton)
        addButton.snp.makeConstraints { make in
            make.left.equalToSuperview().offset(10)
            make.top.equalTo(tableView.snp.bottom).offset(10)
            make.size.equalTo(CGSize(width: 100, height: 40))
        }
    }
    
    private func createDataSources() {
        getImages()
        readTxtContent()
        initializeData()
    }
    
    private func initializeData() {
        for _ in 1...10 {
            addItem()
        }
    }
    
    private func addItem() {
        if let item = addRandomItem() {
            items.append(item)
            updateButtonStates()
        }
    }
    
    private func addRandomItem() -> HomeDataItem? {
        if availableImages.isEmpty || availableTexts.isEmpty {
            return nil
        }
        
        let randomImageIndex = Int.random(in: 0..<availableImages.count)
        let randomTextIndex = Int.random(in: 0..<availableTexts.count)
        
        let image = availableImages.remove(at: randomImageIndex)
        let text = availableTexts.remove(at: randomTextIndex)
        return HomeDataItem(imageName: image, title: text)
    }
    
    @objc private func deleteAction() {
        if items.count >= 2 {
            deleteItem(at: 1)
        } else if items.count == 1 {
            deleteItem(at: 0)
        }
    }
    
    @objc private func addAction() {
        if items.count < 3 {
            addItem()
            self.tableView.reloadData()
        } else if items.count < 20 {
            if let item = addRandomItem() {
                items.insert(item, at: 2)
                tableView.insertRows(at: [IndexPath(row: 2, section: 0)], with: .bottom)
            }
            if (self.items.count == 20) {
                addButton.isEnabled = false
            }
        }
    }
    
    private func updateButtonStates() {
        deleteButton.isEnabled = items.count > 0
        addButton.isEnabled = items.count < 20
    }
    
    private func deleteItem(at index: Int) {
        guard index < items.count else { return }
        let item = items.remove(at: index)
      
        availableImages.append(item.imageName)
        availableTexts.append(item.title)
        
        tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade)
        updateButtonStates()
    }
    
    private func getImages() {
        for i in 1...25 {
            let name = String(format: "thumb%02d", i)
            availableImages.append(name)
        }
    }
    
    private func readTxtContent()  {
        guard let path = Bundle.main.path(forResource: "strings", ofType: "txt") else {
            print("File not found.")
            return
        }
        do {
            let content = try String(contentsOfFile: path, encoding: .utf8)
            availableTexts = content.split(separator: "\n").map { String($0.trimmingCharacters(in: .whitespaces)) }
        } catch {
            print("Error reading file: \(error)")
        }
    }
}

extension ViewController: UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected item: \(items[indexPath.row])")
    }
}

extension ViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! HomeTableViewCell
        cell.model = items[indexPath.row]
        cell.selectionStyle = .none
        return cell
    }
}

class HomeTableViewCell: UITableViewCell {
    
    var model: HomeDataItem? {
        didSet {
            if let model = model {
                avatarImageView.image = UIImage(named: model.imageName)
                titleLabel.text = model.title
            }
        }
    }
    
    private let maskLayer = CALayer()
    
    private lazy var avatarImageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
    
    private lazy var titleLabel: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 15)
        label.textColor = .white
        return label
    }()
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
        // Configure the view for the selected state
    }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        maskLayer.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
        if let maskImage = UIImage(named: "frame") {
            maskLayer.contents = maskImage.cgImage
        }
        self.avatarImageView.layer.mask = maskLayer
        
        setupUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupUI() {
        self.contentView.backgroundColor = .clear
        self.backgroundColor = .clear
        contentView.addSubview(avatarImageView)
        avatarImageView.snp.makeConstraints { make in
            make.centerY.equalToSuperview()
            make.left.equalToSuperview().offset(15)
            make.size.equalTo(40)
        }
        
        contentView.addSubview(titleLabel)
        titleLabel.snp.makeConstraints { make in
            make.centerY.equalToSuperview()
            make.left.equalTo(avatarImageView.snp.right).offset(10)
        }
    }
}

posted on 2019-01-14 23:08  youhui  阅读(1471)  评论(0编辑  收藏  举报