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 |