๋ฐ์ํ
์ ์
์ค๋ธ์ ํธ๋ฅผ Model, View, ViewModel๋ก ๋ถ๋ฆฌํ๋ ๋์์ธ ํจํด
Model
- ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ ์
- ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์์
- structs ํน์ ์์ฃผ ๊ฐ๋จํ class๋ก ๊ตฌํ
View
- ์ฌ์ฉ์์ ์ํธ ์์ฉํ๊ณ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๊ฑฐ๋ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ๊ฒฝ์ฐ ViewModel์๊ฒ ์๋ ค์ค
- UI๋ฅผ ํ์ํ๊ธฐ ์ํ ์ฝ๋๋ฅผ ํฌํจํ๊ณ ์ด ์ธ์ ViewModel์ ์๋ ๋น์ฆ๋์ค ๋ก์ง์ ํธ์ถํ๋ ์ฝ๋๊ฐ ๋ค์ด ์์
ViewModel
- view๋ก ๋ถํฐ ์ฌ์ฉ์์ ์ํธ์์ฉ์ ๋ฐ๊ณ ์ด์ ๋ง๋ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ฒ ๋จ
- UIKit ๋ถํ์
- ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธ ํ๊ณ ๋ทฐ ์์๋ฅผ ์ ๋ฐ์ดํธํจ
- model ์ ๋ณด view์ fetch ํ๊ธฐ ์ํด ๊ทธ ๊ฐ์ ๋ณ๊ฒฝ (๋ทฐ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ํ๋ค๋ ๋ป)
์ฅ์
- ๊ธฐ์กด MVC์์ ๋ง์ ์ญํ์ ๋ด๋นํ๋ ViewController์ ์ญํ์ ViewModel์์ ์ฒ๋ฆฌํ ์ ์์
๋จ์
- ์ค๊ณ ์ด๋ ค์ (์ ์ฒด๋ค MVVM์ ๊ฐ์ง ํ์๋ ์์. ํ์ํ ๊ณณ์์ MVVM์ผ๋ก ๊ตฌํ)
- ๋ทฐ์ ๋ํ ์ฒ๋ฆฌ ๋ณต์กํ ์๋ก ViewModel์ ํฌ๊ธฐ๊ฐ ์ปค์ง
tutorial
์ ์ฒด ์ฝ๋ ๋งํฌ ๐๐ป ๊นํ๋ธ ๋ฐ๋ก๊ฐ๊ธฐ
Model ์์ฑ
์ฌ์ฉํ api : https://jsonplaceholder.typicode.com/posts
- userId, id, title, body ๊ฐ ํ๋์ post, post๋ฅผ ์ด๋ ์ด๋ก ๋ฐํํ๋ api
import Foundation
typealias Posts = [Post]
struct Post: Codable {
let userID, id: Int
let title, body: String
enum CodingKeys: String, CodingKey {
case userID = "userId"
case id, title, body
}
}
- Model ํด๋์ Post.swift ํ์ผ์ ์์ฑํจ.
- json ๊ตฌ์กฐ๋ฅผ Codable๋ก ์ ์
- ํ: https://app.quicktype.io/ json ์ ์ฌ๊ธฐ์ ๋ณต๋ถํ๋ฉด ๋ฉ์ง ๋ฐ์ดํฐ ๋ชจ๋ธ ์ฝ๋๋ฅผ ๋ง๋ค์ด ์ค๋๋ค! ๋ค๋ง ๋ณ์๋ช , ํด๋์ค๋ช ์ด๋ฐ๊ฑฐ ๋ณ๊ฒฝํด์ผ ํ ์๋ ์๊ณ ์์ ์ด ํ์ํ ๊ฒฝ์ฐ๋ ์์ผ๋ ํ ์คํธ ํ ๋๋ง ์ฐธ๊ณ ...
Service ์์ฑ
import Foundation
class PostNetworkManager {
static let shared = PostNetworkManager()
private init() { }
func getPosts(completion: @escaping (Bool, Posts?) -> ()) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil else {
completion(false, nil)
print("error ::: \(error!)")
return
}
guard let response = response as? HTTPURLResponse, (200 ..< 300) ~= response.statusCode else {
completion(false, nil)
return
}
do {
let model = try JSONDecoder().decode(Posts.self, from: data!)
completion(true, model)
} catch {
completion(false, nil)
}
}.resume()
}
}
- Service ํด๋์ PostNetworkManager.swift ํ์ผ ์์ฑ
- ๋คํธ์ํน ์ฑ๊ธํค ์์ฑ
- Post ๋ชจ๋ธ๋ก decode ํด์ [Post] ๋ฐฐ์ด ๋ฐํ (์ฝ๋์์๋ Posts)
ViewModel ์์ฑ
import Foundation
class PostViewModel {
var tableReload: (() -> Void)?
var posts: Posts? = [Post]() {
// ํ
์ด๋ธ ๋ทฐ ๋ฆฌ๋ก๋ ์ฒ๋ฆฌ
didSet {
tableReload?()
}
}
func fetchData() {
PostNetworkManager.shared.getPosts { success, data in
if success {
self.posts = data
} else {
// ์๋ฌ ์ฒ๋ฆฌ
}
}
}
}
- ViewModel์ ๋น์ฆ๋์ค ๋ก์ง, ๋ฐ์ดํฐ request ๋ฐ ๊ฐ๊ณต ๋ฑ์ ์ฒ๋ฆฌํจ. UIKit ์ํฌํธ ๋ถํ์
- ViewController์๋ UI ๊ด๋ จ๋ ์ฝ๋๋ฅผ ํฌํจํ๊ณ ๋ฐ์ดํฐ๋ ๋น์ฆ๋์ค ๋ก์ง์ ViewModel์ ํฌํจ์ํด
- UITableViewCell์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ๋ ViewModel์ ์์ฑํ๊ธฐ๋ ํจ
- ๋ฐ์ดํฐ์ ๊ฐ๊ณต๋ ViewModel์์ ์ฒ๋ฆฌ
View ์์ฑ
import UIKit
class PostViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
let viewModel = PostViewModel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
tableView.backgroundColor = .gray
tableView.delegate = self
tableView.dataSource = self
tableView.register(PostTableViewCell.nib, forCellReuseIdentifier: PostTableViewCell.identifier)
viewModel.tableReload = {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
viewModel.fetchData()
}
}
extension PostViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.posts?.count ?? 0
}
}
extension PostViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PostTableViewCell.identifier, for: indexPath) as? PostTableViewCell else {
return UITableViewCell()
}
if let data = viewModel.posts?[indexPath.row] {
cell.data = data
}
return cell
}
}
import UIKit
class PostTableViewCell: UITableViewCell {
@IBOutlet weak var userLabel: UILabel!
@IBOutlet weak var idLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var bodyLabel: UILabel!
var data: Post? {
didSet {
userLabel.text = "\(data?.userID ?? 0)"
idLabel.text = "\(data?.id ?? 0)"
titleLabel.text = data?.title
bodyLabel.text = data?.body
}
}
static var identifier: String {
return String(describing: self)
}
static var nib: UINib {
return UINib(nibName: identifier, bundle: nil)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
์ฐธ๊ณ ๋์ ๋ฐ ์นํ์ด์ง
์ฝ๋๋ ์๋ ์ถ์ฒ์์ ๊ฐ์ ธ์ค์ง ์์์ ์ฝ๋ ์ฎ๊ธฐ์ค๋ ์ถ์ฒ ํ๊ธฐ ๋ถํ๋๋ ค์!
728x90
๋ฐ์ํ
'๐ iOS' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[iOS/Swift] UICollectionView scroll animation (0) | 2022.06.06 |
---|---|
[iOS/Swift] UICollectionViewFlowLayout ์์๋ณด๊ธฐ (0) | 2022.06.06 |
[Swift/CoreAnimation] CABasicAnimation ์ด๋ (0) | 2022.06.02 |
[iOS/Swift] UICollectionView paging ๋์ปฅ ๊ฑฐ๋ฆฌ๋ ์ด์ ์์ ํ๊ธฐ (0) | 2022.06.01 |
[iOS/Swift] Carousel Effect - UIEdgeInset์ด ์๋ UICollectionView ํ์ด์ง ํ๊ธฐ (0) | 2022.05.31 |
๋๊ธ