swift - tableViewController MVVM 으로 만들기
📌 데이터
{
"data": {
"id": 1,
"fullName": "Tom Sawyer",
"pictureUrl": "tom",
"email": "tomSawyer@gmail.com",
"about": "I like travelling along Missisipi!",
"friends": [
{
"name": "Huckleberry Finn",
"pictureUrl": "finn"
},
{
"name": "Becky Thatcher",
"pictureUrl": "becky"
},
{
"name": "Celeste Holm",
"pictureUrl": "holm"
}
],
"profileAttributes": [
{
"key": "Date of birth",
"value": "November 30, 1835"
},
{
"key": "Gender",
"value": "male"
},
{
"key": "Height",
"value": "180"
}
]
},
"error": 0,
"message": "Success"
}
📌 STEP1. 데이터 모델 작성하기
//MARK: STEP1. 데이터 모델 작성하기
import Foundation
public func dataFromFile(_ filename : String) ->Data?{
@objc class TestClass: NSObject{ }
let bundle = Bundle(for: TestClass.self)
if let path = bundle.path(forResource: filename, ofType: "json"){
return (try? Data(contentsOf: URL(fileURLWithPath: path)))
}
return nil
}
//프로필 데이터 모델
class Profile {
var fullName:String?
var pictureUrl:String?
var email:String?
var about:String?
var friends = [Friend]()
var profileAttributes = [Attribute]()
//return nil 가능
init?(data:Data) {
do{
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any], let body = json["data"] as? [String:Any]{
self.fullName = body["fullName"] as? String
self.pictureUrl = body["pictureUrl"] as? String
self.about = body["about"] as? String
self.email = body["email"] as? String
//json 데이터에서 배열 가져오기
if let friends = body["friends"] as? [[String:Any]]{
//map = for문
self.friends = friends.map{
Friend(json: $0)
}
}
//json 데이터에서 배열 가져오기
if let profileAttributes = body["profileAttributes"] as? [[String:Any]]{
self.profileAttributes = profileAttributes.map{
Attribute(json: $0)
}
}
}
}catch{
print("Error deserializing JSON: \(error)")
return nil
}
}
}
//친구 데이터 모델
class Friend {
var name:String?
var pictureUrl:String?
init(json:[String:Any]) {
self.name = json["name"] as? String
self.pictureUrl = json["pictureUrl"] as? String
}
}
//특성 데이터 모델
class Attribute {
var key:String?
var value:String?
init(json:[String:Any]) {
self.key = json["key"] as? String
self.value = json["value"] as? String
}
}
📌 ViewModel 작성하기
STEP2. Enum으로 CELL 타입 작성하기
STEP3. 각 셀들이 가져야할 속성 및 메소드 정의를 위한 프로토콜 작성
STEP4. 각셀들 객체 뷰 모델 작성
STEP5. ViewModel 초기화
STEP6. DataSource 생성
import UIKit
import Foundation
//MARK: STEP2. CELL 타입 작성하기
enum ProfileViewModelItemType {
case nameAndPicture
case about
case email
case friend
case attribute
}
//MARK: STEP3. 프로토콜 작성
//각 셀들이 꼭 가져야될 값
protocol ProfileViewModelItem {
var type : ProfileViewModelItemType{ get }
var sectionTitle:String { get }
var rowCount : Int { get }
}
class ProfileViewModel: NSObject {
var items = [ProfileViewModelItem]()
//MARK: STEP5. ViewModel 초기화
override init() {
super.init()
guard let data = dataFromFile("ServerData") , let profile = Profile(data: data) else {
return
}
//이름+ 이미지 아이템
if let name = profile.fullName , let picUrl = profile.pictureUrl {
let nameAndPictureItem = ProfileViewModelNamePictureItem(name: name, pictureUrl: picUrl)
//배열 0번째 index
items.append(nameAndPictureItem)
}
if let about = profile.about{
let aboutItem = ProfileViewModelAboutItem(about: about)
//배열 1번째 index
items.append(aboutItem)
}
if let email = profile.email{
let emailItem = ProfileViewModelEmailItem(email: email)
//배열 2번째 index
items.append(emailItem)
}
let attributes = profile.profileAttributes
if !attributes.isEmpty {
let attributesItem = ProfileViewModelAttributesItem(attributes: attributes)
//배열 3번째 index
items.append(attributesItem)
}
let friends = profile.friends
if !friends.isEmpty {
let friendsItem = ProfileViewModelFriendsItem(friends: friends)
//배열 4번째 index
items.append(friendsItem)
}
}
}
//MARK: STEP6. DataSource 생성
extension ProfileViewModel:UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items[section].rowCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.section]
switch item.type {
case .nameAndPicture:
if let cell = tableView.dequeueReusableCell(withIdentifier: NamePictureCell.identifier, for: indexPath) as? NamePictureCell{
cell.item = item
return cell
}
case .about:
if let cell = tableView.dequeueReusableCell(withIdentifier: AboutCell.identifier, for: indexPath) as? AboutCell{
cell.item = item
return cell
}
case .email:
if let cell = tableView.dequeueReusableCell(withIdentifier: EmailCell.identifier, for: indexPath) as? EmailCell{
cell.item = item
return cell
}
case .friend:
if let item = item as? ProfileViewModelFriendsItem,
let cell = tableView.dequeueReusableCell(withIdentifier: FriendCell.identifier, for: indexPath) as? FriendCell {
let friend = item.friends[indexPath.row]
cell.item = friend
return cell
}
case .attribute:
if let item = item as? ProfileViewModelAttributesItem,
let cell = tableView.dequeueReusableCell(withIdentifier: AttributesCell.identifier, for: indexPath) as? AttributesCell{
cell.item = item.attributes[indexPath.row]
return cell
}
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return items[section].sectionTitle
}
}
//MARK: STEP4. 각셀 객체 뷰 모델
//이름+ 이미지
class ProfileViewModelNamePictureItem: ProfileViewModelItem {
var type : ProfileViewModelItemType {
return .nameAndPicture
}
var sectionTitle: String{
return "Main Info"
}
var rowCount: Int {
return 1
}
var name : String
var pictureUrl : String
init(name:String, pictureUrl:String) {
self.name = name
self.pictureUrl = pictureUrl
}
}
//about
class ProfileViewModelAboutItem: ProfileViewModelItem {
var type : ProfileViewModelItemType {
return .about
}
var sectionTitle: String{
return "About"
}
var rowCount: Int {
return 1
}
var about : String
init(about:String) {
self.about = about
}
}
//email
class ProfileViewModelEmailItem: ProfileViewModelItem {
var type : ProfileViewModelItemType {
return .email
}
var sectionTitle: String{
return "Email"
}
var rowCount: Int {
return 1
}
var email : String
init(email:String) {
self.email = email
}
}
//Attributes
class ProfileViewModelAttributesItem: ProfileViewModelItem {
var type : ProfileViewModelItemType {
return .attribute
}
var sectionTitle: String{
return "Attributes"
}
var attributes: [Attribute]
var rowCount: Int {
return attributes.count
}
init(attributes:[Attribute]) {
self.attributes = attributes
}
}
//friends
class ProfileViewModelFriendsItem: ProfileViewModelItem {
var type : ProfileViewModelItemType {
return .attribute
}
var sectionTitle: String{
return "Friends"
}
var friends: [Friend]
var rowCount: Int {
return friends.count
}
init(friends:[Friend]) {
self.friends = friends
}
}
📌 Cell 작성
NamePictureCell.swift
import UIKit
class NamePictureCell: UITableViewCell {
@IBOutlet weak var nameLabel: UILabel?
@IBOutlet weak var pictureImageView: UIImageView?
var item : ProfileViewModelItem?{
didSet{
//다운 캐스팅
guard let item = item as? ProfileViewModelNamePictureItem else { return }
nameLabel?.text = item.name
pictureImageView?.image = UIImage(named: item.pictureUrl)
}
}
static var identifier:String {
return String(describing: self)
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
override func awakeFromNib() {
super.awakeFromNib()
pictureImageView?.layer.cornerRadius = 50
pictureImageView?.clipsToBounds = true
pictureImageView?.contentMode = .scaleAspectFit
pictureImageView?.backgroundColor = UIColor.lightGray
}
override func prepareForReuse() {
super.prepareForReuse()
pictureImageView?.image = nil
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
AboutCell.swift
import UIKit
class AboutCell: UITableViewCell {
@IBOutlet weak var aboutLabel: UILabel?
var item: ProfileViewModelItem?{
didSet{
guard let item = item as? ProfileViewModelAboutItem else {
return
}
aboutLabel?.text = item.about
}
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
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
}
}
EmailCell.swift
import UIKit
class EmailCell: UITableViewCell {
@IBOutlet weak var emailLabel: UILabel?
var item: ProfileViewModelItem? {
didSet {
guard let item = item as? ProfileViewModelEmailItem else {
return
}
emailLabel?.text = item.email
}
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
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
}
}
FriendCell.swift
import UIKit
class FriendCell: UITableViewCell {
@IBOutlet weak var pictureImageView: UIImageView?
@IBOutlet weak var nameLabel: UILabel?
var item: Friend?{
didSet{
guard let item = item else {
return
}
if let pictureUrl = item.pictureUrl{
pictureImageView?.image = UIImage(named: pictureUrl)
}
nameLabel?.text = item.name
}
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
override func awakeFromNib() {
super.awakeFromNib()
pictureImageView?.layer.cornerRadius = 40
pictureImageView?.clipsToBounds = true
pictureImageView?.contentMode = .scaleAspectFit
pictureImageView?.backgroundColor = UIColor.lightGray
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() {
super.prepareForReuse()
pictureImageView?.image = nil
}
}
AttributesCell.swift
import UIKit
class AttributesCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel?
@IBOutlet weak var valueLabel: UILabel?
var item: Attribute?{
didSet{
titleLabel?.text = item?.key
valueLabel?.text = item?.value
}
}
static var nib:UINib {
return UINib(nibName: identifier, bundle: nil)
}
static var identifier: String {
return String(describing: self)
}
}
📌 ViewController 작성
import UIKit
class SearchVC: UIViewController {
fileprivate let viewModel = ProfileViewModel()
@IBOutlet weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
tableView?.dataSource = viewModel
tableView?.estimatedRowHeight = 100
tableView?.rowHeight = UITableView.automaticDimension
tableView?.register(AboutCell.nib, forCellReuseIdentifier: AboutCell.identifier)
tableView?.register(NamePictureCell.nib, forCellReuseIdentifier: NamePictureCell.identifier)
tableView?.register(FriendCell.nib, forCellReuseIdentifier: FriendCell.identifier)
tableView?.register(AttributesCell.nib, forCellReuseIdentifier: AttributesCell.identifier)
tableView?.register(EmailCell.nib, forCellReuseIdentifier: EmailCell.identifier)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
'아이폰 개발 > Swift' 카테고리의 다른 글
swift - custom collectionview cell 주의 (0) | 2021.06.26 |
---|---|
Swift 스크롤 뷰 튜토리얼 2021 - 06 -03 (0) | 2021.06.03 |
swift 공통 로그 (0) | 2021.04.14 |
swift -Realm 예제 2 - ToDoList 일대다 관계 (0) | 2021.04.12 |
swift -Realm 예제 1 - 단순 CRUD (0) | 2021.04.12 |