sladuf
200
sladuf
์ „์ฒด ๋ฐฉ๋ฌธ์ž
์˜ค๋Š˜
์–ด์ œ
  • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (83)
    • ๐Ÿ“š Programming (32)
      • Swift (13)
      • JAVA (2)
      • Python (6)
      • SQL (6)
      • Web (5)
    • ๐Ÿ“ฑ iOS (25)
      • Base (7)
      • SwiftUI (9)
      • UIKit (7)
      • ์ธ๊ฐ• & ์ฑ… (2)
    • ๐Ÿ”— Algorithm (20)
      • Python (12)
      • Swift (3)
      • Tip (5)
    • ๐Ÿ—‚ ETC (6)

๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

  • ํ™ˆ
  • ํƒœ๊ทธ
  • ๋ฐฉ๋ช…๋ก

๊ณต์ง€์‚ฌํ•ญ

์ธ๊ธฐ ๊ธ€

ํƒœ๊ทธ

  • ์Šค์œ„ํ”„ํŠธ
  • Swift

์ตœ๊ทผ ๋Œ“๊ธ€

์ตœ๊ทผ ๊ธ€

ํ‹ฐ์Šคํ† ๋ฆฌ

๊ธ€์“ฐ๊ธฐ ์„ค์ •
hELLO ยท Designed By ์ •์ƒ์šฐ.
sladuf

200

[UIKit] TableView Multiple Cell, multi type json data parsing
๐Ÿ“ฑ iOS/UIKit

[UIKit] TableView Multiple Cell, multi type json data parsing

2023. 6. 27. 01:11

 


 

TableView๋Š” ํ•˜๋‚˜์˜ Cell์ด ์•„๋‹Œ ๋‹ค์–‘ํ•œ Cell์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ข…์ข… ์•ฑ์„ ๋ณด๋‹ค๋ณด๋ฉด tableview ์ค‘๊ฐ„์ค‘๊ฐ„ ๊ด‘๊ณ ๊ฐ€ ๋‚˜์˜จ๋‹ค๊ฑฐ๋‚˜, ๋‚˜๋ฅผ ์œ„ํ•œ ์ถ”์ฒœ์„ ํ•œ๋‹ค๊ฑฐ๋‚˜ ...

๋Œ€๋ถ€๋ถ„์˜ ์•ฑ ์„œ๋น„์Šค๊ฐ€ tableview๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , multiple cell์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ €๋„ multiple cell์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์•ฑ์„ ๋งŒ๋“ค์–ด ๋ณผ๊ฒƒ์ž…๋‹ˆ๋‹ค!

 

๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

๋จผ์ € ๋ฉ€ํ‹ฐํƒ€์ž… ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

(JSON์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ์ ‘์€๊ธ€์— ์žˆ์Šต๋‹ˆ๋‹ค)

๋”๋ณด๊ธฐ
let JSONData =
"""
{
    "data" : [
        {
            "type" : "TITLE_ONLY",
            "title" : "Struct vs Class"
        },
        {
            "type" : "IMAGE_ONLY",
            "imageURL" : "https://picsum.photos/200"
        },
        {
            "type" : "CONTENT_WITH_TITLE",
            "title" : "struct ํŠน์ง•",
            "content" : "๊ฐ’ํƒ€์ž…, protocol์ฑ„ํƒ ๊ฐ€๋Šฅ, ์ƒ์† ๋ถˆ๊ฐ€๋Šฅ"
        },
        {
            "type" : "CONTENT_WITH_TITLE",
            "title" : "class ํŠน์ง•",
            "content" : "์ฐธ์กฐํƒ€์ž…, protocol์ฑ„ํƒ ๊ฐ€๋Šฅ, ์ƒ์† ๊ฐ€๋Šฅ"
        },
        {
            "type" : "IMAGE_ONLY",
            "imageURL" : "https://picsum.photos/200"
        },
        {
            "type" : "TITLE_ONLY",
            "title" : "Collection"
        },
        {
            "type" : "CONTENT_WITH_TITLE",
            "title" : "Array",
            "content" : "๋ฐฐ์—ด์„ ์“ฐ๋ฉด ramdom access๊ฐ€๋Šฅํ•จ"
        },
        {
            "type" : "CONTENT_WITH_TITLE",
            "title" : "Set",
            "content" : "์ค‘๋ณต์ด ์‹ซ์€ ์‚ฌ๋žŒ Set์„ ์จ๋ณด์ž"
        },
        {
            "type" : "CONTENT_WITH_TITLE",
            "title" : "Dictonary",
            "content" : "key-value๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹จ๋‹ค"
        },
        {
            "type" : "IMAGE_ONLY",
            "imageURL" : "https://picsum.photos/200"
        }
    ]
}
""".data(using: .utf8)!

 

 

๋จผ์ € ๋ฐฐ์—ด์€ ์˜ค๋กœ์ง€ ํ•˜๋‚˜์˜ ํƒ€์ž…๋งŒ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— CellType์ด๋ผ๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๊ฐ€์ง€๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

struct CellData : Decodable {
    let data : [CellType]
}

 

์—ฌ๊ธฐ์„œ ๊ฐ€์žฅ ์ฃผ๋ชฉํ•ด์•ผํ•  ์ ์€ ๋ฐฐ์—ด์•ˆ์— ๋“ค์–ด ์žˆ๋Š” ๋ฐ์ดํ„ฐ์˜ ๋ชจ์–‘์ด ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด ์ „์— ์ž‘์„ฑํ–ˆ๋˜ ๊ธ€์˜ BaseNetworkManager๋Š” JSONํŒŒ์‹ฑ์„ ํ•˜๊ธฐ์ „์— ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— requestํ•  ๋•Œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

https://990427.tistory.com/117

 

[Swift] ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ NetworkManager๋งŒ๋“ค๊ธฐ (feat. Generic)

์ง€๋‚œ๋ฒˆ์— ์ด๋Ÿฐ ๊ธ€์„ ์ผ์—ˆ๋Š”๋ฐ, https://990427.tistory.com/112 [Swift] json value type์ด ๋‹ฌ๋ผ์ง€๋Š” response์— ๋Œ€์‘ํ•˜์—ฌ API Responder๋งŒ๋“ค๊ธฐ ์š”์ฆ˜ Set์ด ๋œ ๋‚˜์˜ ๊ฐœ๋ฐœ ์Šคํƒ€์ผ,, ์ค‘๋ณต์„ ๋‹ค ์ œ๊ฑฐํ•ด๋ฒ„๋ฆฌ์ž ์ง€๊ธˆ ํ•˜๊ณ  ์žˆ

990427.tistory.com

 

 

๊ทธ๋ž˜์„œ multi type์„ ๊ฐ€์ง„ ๋ฐฐ์—ด์˜ ๊ฒฝ์šฐ ์œ„์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ํŒŒ์‹ฑ์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ œ์ผ ์‰ฌ์šด๋ฐฉ๋ฒ•์€ ๊ฐ๊ฐ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅด์ง€๋งŒ, type๋งŒ์€ ์ผ์น˜ํ•˜๋ฏ€๋กœ type์„ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ํ”„๋กœํผํ‹ฐ๋ฅผ ์˜ต์…”๋„ ์ฒ˜๋ฆฌ ํ•ด์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

struct CellType: Decodable {
    let type: String
    let title: String?
    let content: String?
    let imageURL: String?
}

 

๋ฌผ๋ก  ์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

ํ•˜์ง€๋งŒ, ๊ฐ๊ฐ์˜ cell๋ฐ์ดํ„ฐ๋“ค์ด ์žˆ์ง€๋„ ์•Š์€ ํ”„๋กœํผํ‹ฐ๋ฅผ ์˜ต์…”๋„ ํƒ€์ž…์œผ๋กœ ๊ฐ€์ง€๊ฒŒ ๋˜๋ฏ€๋กœ ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์ด ์œ„๋ฐฐ๋˜๋Š”๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ง€๊ธˆ์€ ํ”„๋กœํผํ‹ฐ์˜ ์–‘์ด ์ž‘์ง€๋งŒ, cell์˜ ํƒ€์ž…์ด ๋Š˜์–ด๋‚˜๊ฒŒ๋˜๋ฉด ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋งŽ์•„์ง€๋ฉด์„œ ๋ณต์žกํ•˜๊ณ  ๊ฐ€๋…์„ฑ์„ ํ•ด์น˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ ๊ฐ๊ฐ์˜ cell type์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„๋ฆฌ ํ–ˆ์Šต๋‹ˆ๋‹ค.

struct TitleOnly: Decodable{
    let type: String
    let title: String
}

struct ImageOnly: Decodable{
    let type: String
    let imageURL: String
}

struct ContentWithTitle: Decodable{
    let type: String
    let title: String
    let content: String
}

 

์—ฌ๊ธฐ์„œ CellType์„ ์–ด๋–ป๊ฒŒ ์ž‘์„ฑํ•ด์•ผ ์ด ๊ตฌ์กฐ์ฒด๋ฅผ ๋ชจ๋‘ ํ’ˆ์„ ์ˆ˜ ์žˆ์„๊นŒ?

 

์ด ๊ณ ๋ฏผ์— ๋Œ€ํ•œ ๊ฒฐ๋ก ์€ CellType์„ enum์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

enum์€ ์—ฐ๊ด€๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ํŠน์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์—ฐ๊ด€๊ฐ’์— parsingํ•  ๊ตฌ์กฐ์ฒด๋ฅผ ๋„ฃ์–ด์ฃผ์–ด CellType์„ ๊ตฌํ˜„ํ•˜๋ฉด ๋ชจ๋“  ๊ตฌ์กฐ์ฒด๋ฅผ ํ’ˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

enum CellType: Decodable {
    case TitleOnlyType(TitleOnly)
    case ImageOnlyType(ImageOnly)
    case ContentWithTitleType(ContentWithTitle)
}

 

๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์‹ฑํ–ˆ์„ ๋•Œ ๊ฐ๊ฐ์˜ case์— ๋งž๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ฃผ๊ธฐ ์œ„ํ•ด ์ด๋‹ˆ์…œ๋ผ์ด์ €๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

    enum CodingKeys: String, CodingKey {
        case type
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(String.self, forKey: .type)
        
        switch type {
        case "TITLE_ONLY":
            self = .TitleOnlyType(try decoder.singleValueContainer().decode(TitleOnly.self))
        case "IMAGE_ONLY":
            self = .ImageOnlyType(try decoder.singleValueContainer().decode(ImageOnly.self))
        case "CONTENT_WITH_TITLE":
            self = .ContentWithTitleType(try decoder.singleValueContainer().decode(ContentWithTitle.self))
        default:
            throw DecodingError.dataCorruptedError(forKey: .type, in: container, debugDescription: "Invalid type: \(type)")
        }
    }

 

๋งŒ์•ฝ ๋‹ค๋ฅธ cell์ด ์ถ”๊ฐ€ ๋œ๋‹ค๋ฉด cell type์— ๋งž๋Š” ๊ตฌ์กฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด CellType์˜ case๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์ด๋‹ˆ์…œ๋ผ์ด์ €์— CodingKey๋กœ type์„ ์‚ฌ์šฉํ•œ ์ด์œ ๋Š”, type์ด๋ผ๋Š” key๊ฐ’์ด ๋ชจ๋“  cell data์— ํ•ด๋‹นํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์ด type๊ฐ’์„ ํ†ตํ•ด case๋ฅผ ๊ตฌ๋ถ„์ง€์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

CellType์—์„œ ์–ด๋–ค case๋ฅผ ์ €์žฅํ• ์ง€ ๊ฒฐ์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, json์„ parsingํ•  ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

var cellData = [CellType]()

func fetchData(){
    do {
        let decodedData = try JSONDecoder().decode(CellData.self, from: JSONData)
        cellData = decodedData.data
    } catch {
        print("JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜: \(error)")
    }
}

 

 

TableView ์ ์šฉํ•˜๊ธฐ

๊ทธ๋Ÿผ ์ด์ œ TableView์— ๋ฉ€ํ‹ฐ ๋ทฐ ํƒ€์ž…์„ ์ ์šฉํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

์šฐ์„  ๊ฐ๊ฐ์˜ type์— ๋งž๋Š” Cell ์„ธ๊ฐœ๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

(UI์ฝ”๋“œ๋Š” ์ƒ๋žต)

class TitleOnlyCell : UITableViewCell {
    static let ID = "TitleOnlyCell"
    var titleLabel = UILabel()
    
    /// ์ƒ๋žต...
    
    func bind(_ data: TitleOnly) {
        titleLabel.text = data.title
    }
    override func prepareForReuse() {
        titleLabel.text = nil
    }
}


class ImageOnlyCell : UITableViewCell {
    static let ID = "ImageOnlyCell"
    var image = UIImageView()
    
    /// ์ƒ๋žต...
    
    func bind(_ data: ImageOnly){
        DispatchQueue.global().async { [weak self] in
            if let data = try? Data(contentsOf: URL(string: data.imageURL)!) {
                DispatchQueue.main.async {
                    self?.image.image = UIImage(data: data)
                }
            }
        }
    }
    
    override func prepareForReuse() {
        image.image = nil
    }
}


class ContentWithTitleCell : UITableViewCell {
    static let ID = "ContentWithTitleCell"
    var titleLabel = UILabel()
    var contentLabel = UILabel()
    
    /// ์ƒ๋žต...
    
    func bind(_ data: ContentWithTitle){
        titleLabel.text = data.title
        contentLabel.text = data.content
    }
    
    override func prepareForReuse() {
        titleLabel.text = nil
        contentLabel.text = nil
    }
}

 

๊ทธ๋ฆฌ๊ณ  tableView์— ๋“ฑ๋กํ•ด์ค๋‹ˆ๋‹ค.

        tableView.register(TitleOnlyCell.classForCoder(), forCellReuseIdentifier: TitleOnlyCell.ID)
        tableView.register(ImageOnlyCell.classForCoder(), forCellReuseIdentifier: ImageOnlyCell.ID)
        tableView.register(ContentWithTitleCell.classForCoder(), forCellReuseIdentifier: ContentWithTitleCell.ID)

 

์ด์ œ ์–ด๋–ค cell์„ ์„ ํƒํ•  ๊ฒƒ์ธ๊ฐ€ ๊ฒฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

tableView๊ฐ€ ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ์ธ cellData๋ฅผ ์ฝ์–ด, ํ•ด๋‹น ๋ฐ์ดํ„ฐ์˜ case์— ๋”ฐ๋ผ ๋ณด์—ฌ์ค„ cell์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

extension ViewController : UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cellData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch cellData[indexPath.row] {
        case .TitleOnlyType(let data):
            let cell = tableView.dequeueReusableCell(withIdentifier: TitleOnlyCell.ID) as! TitleOnlyCell
            cell.bind(data)
            return cell
        case .ImageOnlyType(let data):
            let cell = tableView.dequeueReusableCell(withIdentifier: ImageOnlyCell.ID) as! ImageOnlyCell
            cell.bind(data)
            return cell
        case .ContentWithTitleType(let data):
            let cell = tableView.dequeueReusableCell(withIdentifier: ContentWithTitleCell.ID) as! ContentWithTitleCell
            cell.bind(data)
            return cell
        }
    }
}

 

๋์ž…๋‹ˆ๋‹ค!

 

 

 

Multiple Cell์ด ๊ฐ€์ง€๋Š” ์žฅ์ 

cell type์„ ๋‹ค์–‘ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด๋‘๋ฉด, ์„œ๋ฒ„์˜ ๋ฐ์ดํ„ฐ์— ์˜ํ•ด ๋ณด์—ฌ์ง€๋Š” ํ™”๋ฉด์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ server driven UI๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋ฐ”์ผ ์•ฑ์€ ์œ ์ €๊ฐ€ ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ์•ผ๋งŒ ์ˆ˜์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์ œ์•ฝ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

server driven UI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„œ๋ฒ„์— ์˜ํ•ด UI๊ฐ€ ๋ฐ”๋€Œ๊ธฐ ๋•Œ๋ฌธ์— ์—…๋ฐ์ดํŠธ ์—†์ด๋„ ํ™”๋ฉด์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฆ‰, multiple cell์„ ์ž˜ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋ฐ”์ผ ์•ฑ์˜ ์ œ์•ฝ์‚ฌํ•ญ์„ ์–ด๋А์ •๋„ ํ•ด์†Œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž!

 

 

 

 

 

 

 

 

 

์ €์ž‘์žํ‘œ์‹œ (์ƒˆ์ฐฝ์—ด๋ฆผ)

'๐Ÿ“ฑ iOS > UIKit' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

modalPresentationStyle ์„ค์ • ์‹œ์ (feat.ViewLifeCycle)  (0) 2024.06.14
[UIKit] CustomDatePicker ๋งŒ๋“ค๊ธฐ (3/3)  (0) 2023.04.03
[UIKit] toast message ๋งŒ๋“ค๊ธฐ  (0) 2023.02.16
[UIKit] CustomDatePicker ๋งŒ๋“ค๊ธฐ (2/3)  (0) 2023.01.08
[UIKit] CustomDatePicker ๋งŒ๋“ค๊ธฐ (1/3)  (0) 2023.01.08
    '๐Ÿ“ฑ iOS/UIKit' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
    • modalPresentationStyle ์„ค์ • ์‹œ์ (feat.ViewLifeCycle)
    • [UIKit] CustomDatePicker ๋งŒ๋“ค๊ธฐ (3/3)
    • [UIKit] toast message ๋งŒ๋“ค๊ธฐ
    • [UIKit] CustomDatePicker ๋งŒ๋“ค๊ธฐ (2/3)
    sladuf
    sladuf

    ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”