ios开发简单MVVM+Rxswift案例

2025-05-06  本文已影响0人  博文得礼

iOS项目案例,采用MVVM架构,实现一个简单的电影列表应用,包含网络请求获取电影数据、数据展示、以及点击电影项查看详情的功能。该案例使用了Alamofire进行网络请求,RxSwift进行数据绑定和响应式编程。

1. 首先,创建一个新的iOS项目,并导入所需的第三方库:Alamofire和RxSwift(可以通过CocoaPods或Swift Package Manager导入)。

2. 创建相关文件:

• Movie.swift:定义电影数据模型。

• MovieAPI.swift:负责网络请求获取电影数据。

• MovieListViewModel.swift:视图模型,处理电影列表相关的业务逻辑。

• MovieDetailViewModel.swift:视图模型,处理电影详情相关的业务逻辑。

• MovieListViewController.swift:视图控制器,展示电影列表。

• MovieDetailViewController.swift:视图控制器,展示电影详情。

3. Movie.swift代码如下:

import Foundation

struct Movie {

    let title: String

    let overview: String

    let posterPath: String

    let releaseDate: String

}

4. MovieAPI.swift代码如下:

import Foundation

import Alamofire

class MovieAPI {

    static func fetchPopularMovies(completion: @escaping (Result<[Movie], Error>) -> Void) {

        let url = "https://api.themoviedb.org/3/movie/popular"

        let parameters: [String: Any] = [

            "api_key": "YOUR_API_KEY", // 这里需要替换为你自己的TheMovieDB API Key

            "language": "en-US"

        ]

        AF.request(url, method:.get, parameters: parameters).responseJSON { response in

            if let error = response.error {

                completion(.failure(error))

                return

            }

            guard let data = response.data else {

                completion(.failure(NSError(domain: "MovieAPI", code: -1, userInfo: nil)))

                return

            }

            do {

                let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]

                guard let results = json?["results"] as? [[String: Any]] else {

                    completion(.failure(NSError(domain: "MovieAPI", code: -2, userInfo: nil)))

                    return

                }

                let movies = results.compactMap { dict in

                    guard let title = dict["title"] as? String,

                          let overview = dict["overview"] as? String,

                          let posterPath = dict["poster_path"] as? String,

                          let releaseDate = dict["release_date"] as? String else {

                        return nil

                    }

                    return Movie(title: title, overview: overview, posterPath: posterPath, releaseDate: releaseDate)

                }

                completion(.success(movies))

            } catch {

                completion(.failure(error))

            }

        }

    }

}

5. MovieListViewModel.swift代码如下:

import Foundation

import RxSwift

import RxCocoa

class MovieListViewModel {

    let movies = BehaviorSubject<[Movie]>(value: [])

    let isLoading = BehaviorSubject<Bool>(value: false)

    let error = PublishSubject<Error>()

    private let disposeBag = DisposeBag()

    init() {

        loadMovies()

    }

    func loadMovies() {

        isLoading.onNext(true)

        MovieAPI.fetchPopularMovies { [weak self] result in

            self?.isLoading.onNext(false)

            switch result {

            case.success(let movies):

                self?.movies.onNext(movies)

            case.failure(let error):

                self?.error.onNext(error)

            }

        }

    }

    func transformToDetailViewModel(for movie: Movie) -> MovieDetailViewModel {

        return MovieDetailViewModel(movie: movie)

    }

}

6. MovieDetailViewModel.swift代码如下:

import Foundation

class MovieDetailViewModel {

    let movie: Movie

    init(movie: Movie) {

        self.movie = movie

    }

    var title: String {

        return movie.title

    }

    var overview: String {

        return movie.overview

    }

    var releaseDate: String {

        return movie.releaseDate

    }

    var posterURL: String {

        return "https://image.tmdb.org/t/p/w500\(movie.posterPath)"

    }

}

7. MovieListViewController.swift代码如下:

import UIKit

import RxSwift

import RxCocoa

import Alamofire

class MovieListViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!

    @IBOutlet weak var errorLabel: UILabel!

    private let disposeBag = DisposeBag()

    private var viewModel: MovieListViewModel!

    override func viewDidLoad() {

        super.viewDidLoad()

        viewModel = MovieListViewModel()

        // 绑定加载状态到ActivityIndicator

        viewModel.isLoading

          .bind(to: activityIndicator.rx.isAnimating)

          .disposed(by: disposeBag)

        // 绑定错误信息到Label

        viewModel.error

          .map { $0.localizedDescription }

          .bind(to: errorLabel.rx.text)

          .disposed(by: disposeBag)

        // 注册Cell

        tableView.register(UINib(nibName: "MovieCell", bundle: nil), forCellReuseIdentifier: "MovieCell")

        // 绑定电影数据到TableView

        viewModel.movies

          .bind(to: tableView.rx.items(cellIdentifier: "MovieCell", cellType: MovieCell.self)) { (row, movie, cell) in

                cell.configure(with: movie)

            }

          .disposed(by: disposeBag)

        // 处理Cell点击事件

        tableView.rx.modelSelected(Movie.self)

          .subscribe(onNext: { [weak self] movie in

                let detailViewModel = self?.viewModel.transformToDetailViewModel(for: movie)

                let detailVC = MovieDetailViewController(viewModel: detailViewModel!)

                self?.navigationController?.pushViewController(detailVC, animated: true)

            })

          .disposed(by: disposeBag)

    }

}

8. 创建一个自定义的UITableViewCell类MovieCell.swift:

import UIKit

class MovieCell: UITableViewCell {

    @IBOutlet weak var titleLabel: UILabel!

    @IBOutlet weak var overviewLabel: UILabel!

    func configure(with movie: Movie) {

        titleLabel.text = movie.title

        overviewLabel.text = movie.overview

    }

}

9. MovieDetailViewController.swift代码如下:

import UIKit

class MovieDetailViewController: UIViewController {

    @IBOutlet weak var titleLabel: UILabel!

    @IBOutlet weak var overviewLabel: UILabel!

    @IBOutlet weak var releaseDateLabel: UILabel!

    @IBOutlet weak var posterImageView: UIImageView!

    private var viewModel: MovieDetailViewModel!

    init(viewModel: MovieDetailViewModel) {

        self.viewModel = viewModel

        super.init(nibName: "MovieDetailViewController", bundle: nil)

    }

    required init?(coder: NSCoder) {

        fatalError("init(coder:) has not been implemented")

    }

    override func viewDidLoad() {

        super.viewDidLoad()

        titleLabel.text = viewModel.title

        overviewLabel.text = viewModel.overview

        releaseDateLabel.text = viewModel.releaseDate

        if let url = URL(string: viewModel.posterURL) {

            let task = URLSession.shared.dataTask(with: url) { (data, response, error) in

                if let data = data, let image = UIImage(data: data) {

                    DispatchQueue.main.async {

                        self.posterImageView.image = image

                    }

                }

            }

            task.resume()

        }

    }

}

在这个案例中:

• Movie结构体定义了电影的数据模型。

• MovieAPI负责从TheMovieDB API获取热门电影数据。

• MovieListViewModel处理电影列表的业务逻辑,包括数据加载、状态管理和错误处理,并提供方法转换到电影详情的视图模型。

• MovieDetailViewModel处理电影详情的相关逻辑,将电影数据转换为视图可直接使用的格式。

• MovieListViewController和MovieDetailViewController作为视图控制器,负责视图的展示和与视图模型的交互,通过RxSwift实现数据的绑定和事件的处理。

上一篇 下一篇

猜你喜欢

热点阅读