본문 바로가기
아이폰 개발/Swift

swift - tableViewController MVVM 으로 만들기

by 인생여희 2021. 4. 19.

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.
    }
    */

}

 

 

 

SearchTestTV.zip
1.04MB