✅ 여러 데이터 타입을 하나의 데이터 타입으로 통합하기
통합하는 이유는 추후체 컬렉션뷰를 사용함에 있어서 데이터 관리 용이성 확보 및, 컬렉션뷰의 품질 향상 목적
// 📍 detail 정보
{
"response": {
"header": {
"resultCode": "0000",
"resultMsg": "OK"
},
"body": {
"items": {
"item": [
{
"contentid": "126128",
"contenttypeid": "12",
"heritage1": "0",
"heritage2": "0",
"heritage3": "0",
"infocenter": "대구광역시 동구청 공원녹지과 053-662-2867",
"opendate": "",
"restdate": "연중무휴",
"expguide": "",
"expagerange": "",
"accomcount": "",
"useseason": "",
"usetime": "상시 개방",
"parking": "가능\u003Cbr\u003E요금 (최초 2시간 무료 / 이후 30분 당 400원씩 추가 요금 발생)",
"chkbabycarriage": "없음",
"chkpet": "",
"chkcreditcard": "없음"
}
]
},
"numOfRows": 1,
"pageNo": 1,
"totalCount": 1
}
}
}
// ✅ Common 정보
{
"response": {
"header": {
"resultCode": "0000",
"resultMsg": "OK"
},
"body": {
"items": {
"item": [
{
"contentid": "126128",
"contenttypeid": "12",
"title": "동촌유원지",
"createdtime": "20031105090000",
"modifiedtime": "20250425092225",
"tel": "",
"telname": "",
"homepage": "\u003Ca href=\"https://tour.daegu.go.kr/index.do?menu_id=00002942&menu_link=/front/tour/tourMapsView.do?tourId=KOATTR_115\" target=\"_blank\" title=\"새창 : 홈페이지로 이동\"\u003Ehttps://tour.daegu.go.kr\u003C/a\u003E",
"firstimage": "http://tong.visitkorea.or.kr/cms/resource/86/3488286_image2_1.JPG",
"firstimage2": "http://tong.visitkorea.or.kr/cms/resource/86/3488286_image3_1.JPG",
"cpyrhtDivCd": "Type3",
"areacode": "4",
"sigungucode": "4",
"lDongRegnCd": "27",
"lDongSignguCd": "140",
"lclsSystm1": "VE",
"lclsSystm2": "VE03",
"lclsSystm3": "VE030500",
"cat1": "A02",
"cat2": "A0202",
"cat3": "A02020700",
"addr1": "대구광역시 동구 효목동",
"addr2": "산 234-29",
"zipcode": "41179",
"mapx": "128.6506352387",
"mapy": "35.8826195757",
"mlevel": "6",
"overview": "동촌유원지는 대구시 동쪽 금호강변에 있는 44만 평의 유원지로 오래전부터 대구 시민이 즐겨 찾는 곳이다. 각종 위락시설이 잘 갖춰져 있으며, 드라이브를 즐길 수 있는 도로가 건설되어 있다. 수량이 많은 금호강에는 조교가 가설되어 있고, 우아한 다리 이름을 가진 아양교가 걸쳐 있다. 금호강(琴湖江)을 끼고 있어 예로부터 봄에는 그네뛰기, 봉숭아꽃 구경, 여름에는 수영과 보트 놀이, 가을에는 밤 줍기 등 즐길 거리가 많은 곳이다. 또한, 해맞이다리, 유선장, 체육시설, 실내 롤러스케이트장 등 다양한 즐길 거리가 있어 여행의 재미를 더해준다."
}
]
},
"numOfRows": 1,
"pageNo": 1,
"totalCount": 1
}
}
}
// ✅ Image 정보
{
"response": {
"header": {
"resultCode": "0000",
"resultMsg": "OK"
},
"body": {
"items": {
"item": [
{
"contentid": "126128",
"originimgurl": "http://tong.visitkorea.or.kr/cms/resource/80/3488280_image2_1.JPG",
"imgname": "동촌유원지 (1)",
"smallimageurl": "http://tong.visitkorea.or.kr/cms/resource/80/3488280_image3_1.JPG",
"cpyrhtDivCd": "Type3",
"serialnum": "3488280_9"
},
{
"contentid": "126128",
"originimgurl": "http://tong.visitkorea.or.kr/cms/resource/81/3488281_image2_1.jpg",
"imgname": "동촌유원지 (2)",
"smallimageurl": "http://tong.visitkorea.or.kr/cms/resource/81/3488281_image3_1.jpg",
"cpyrhtDivCd": "Type3",
"serialnum": "3488281_1"
},
{
"contentid": "126128",
"originimgurl": "http://tong.visitkorea.or.kr/cms/resource/83/3488283_image2_1.jpg",
"imgname": "동촌유원지 (4)",
"smallimageurl": "http://tong.visitkorea.or.kr/cms/resource/83/3488283_image3_1.jpg",
"cpyrhtDivCd": "Type3",
"serialnum": "3488283_2"
},
{
"contentid": "126128",
"originimgurl": "http://tong.visitkorea.or.kr/cms/resource/84/3488284_image2_1.jpg",
"imgname": "동촌유원지 (5)",
"smallimageurl": "http://tong.visitkorea.or.kr/cms/resource/84/3488284_image3_1.jpg",
"cpyrhtDivCd": "Type3",
"serialnum": "3488284_4"
},
{
"contentid": "126128",
"originimgurl": "http://tong.visitkorea.or.kr/cms/resource/85/3488285_image2_1.JPG",
"imgname": "동촌유원지 (6)",
"smallimageurl": "http://tong.visitkorea.or.kr/cms/resource/85/3488285_image3_1.JPG",
"cpyrhtDivCd": "Type3",
"serialnum": "3488285_5"
},
{
"contentid": "126128",
"originimgurl": "http://tong.visitkorea.or.kr/cms/resource/87/3488287_image2_1.jpg",
"imgname": "동촌유원지 (8)",
"smallimageurl": "http://tong.visitkorea.or.kr/cms/resource/87/3488287_image3_1.jpg",
"cpyrhtDivCd": "Type3",
"serialnum": "3488287_3"
},
{
"contentid": "126128",
"originimgurl": "http://tong.visitkorea.or.kr/cms/resource/82/3488282_image2_1.JPG",
"imgname": "동촌유원지 (3)",
"smallimageurl": "http://tong.visitkorea.or.kr/cms/resource/82/3488282_image3_1.JPG",
"cpyrhtDivCd": "Type3",
"serialnum": "3488282_12"
}
]
},
"numOfRows": 7,
"pageNo": 1,
"totalCount": 7
}
}
}
✅ 통합한 데이터를 섹션과 아이템으로 구분, 추후에 컬렉션뷰에 사용하기 위함
✅ 통합 데이터를 관리할 목적으로 열거형과 구조체 생성
(열거형은 추후에 컬렉션뷰의 datasource에 사용할 예정)
enum DetailSectionType: Int, CaseIterable {
case common
case intro
case image
var title: String {
switch self {
case .common: return "기본 정보"
case .intro: return "소개"
case .image: return "이미지"
}
}
}
enum DetailItemType: Hashable {
case common(title: String, value: String?)
case intro(title: String, value: String?)
case image(title: String, value: String?)
}
struct DetailSection {
let type: DetailSectionType
let item: [DetailItemType]
}
✅ ViewModel에서 각각의 API메서드를 통해 받아온 데이터를 가지고, 데이터 변환할 메서드 구현
@MainActor
class DetailViewModel: ObservableObject {
// MARK: - Variable
@Published var detailIntro: [IntroInfoItem] = []
@Published var commonIntro: [CommonIntroItem] = []
@Published var detailImageList: [DetailImageItem] = []
@Published var detailTotalModel: [DetailSection] = []
...
/// IntroInfoItem → DetailSection 변환하는 메서드
func makeIntroSection() -> DetailSection? {
guard let item = detailIntro.first else { return nil }
let convertedItems = [
("대표 메뉴", item.firstmenu),
("취급 메뉴", item.treatmenu),
("문의 및 안내", item.infocenterfood),
("영업 시간", item.opentimefood),
("쉬는 날", item.restdatefood),
("주차 시설", item.parkingfood),
("포장 가능", item.packing)
].map { DetailItemType.intro(title: $0.0, value: $0.1) }
return DetailSection(type: .intro, item: convertedItems)
}
/// CommonIntroItem → DetailSection 변환하는 메서드
func makeCommonSection() -> DetailSection? {
guard let item = commonIntro.first else { return nil }
let convertedItems = [
("주소", item.addr1),
("소개", item.overview),
("대표 이미지", item.firstimage),
("가게 이름", item.title),
("카테고리", item.cat3)
].map { DetailItemType.common(title: $0.0, value: $0.1) }
return DetailSection(type: .common, item: convertedItems)
}
/// DetailImageItem → DetailSection 변환하는 메서드
func makeImageSection() -> DetailSection? {
guard !detailImageList.isEmpty else { return nil }
let convertedItems = detailImageList.map {
DetailItemType.image(title: $0.imgname, value: $0.originimgurl)
}
return DetailSection(type: .image, item: convertedItems)
}
/// 데이터 타입을 변환하는 메서드를 통합하는 메서드
func makeAllSections() {
var sections: [DetailSection] = []
if let common = makeCommonSection() {
sections.append(common)
}
if let intro = makeIntroSection() {
sections.append(intro)
}
if let image = makeImageSection() {
sections.append(image)
}
self.detailTotalModel = sections
}
}
✅ ViewController 에서 해당 메서드를 호출
extension EateryDetailViewController {
func fetchDetailAllData(contentId: String, contentTypeId: String) {
Task {
async let common:() = detailVM.fetchCommonIntroInfo(contentId: contentId)
async let intro:() = detailVM.fetchDetailInfo(contentId: contentId, contentType: contentTypeId)
async let image:() = detailVM.fetchDetailImageList(contentId: contentId)
await common
await intro
await image
// 모든 데이터가 완료된 후 섹션 생성
detailVM.makeAllSections()
}
}
private func bindViewModel() {
detailVM.$detailTotalModel
.receive(on: DispatchQueue.main)
.sink { [weak self] sections in
print("✅ 섹션 데이터 수신 완료")
for section in sections {
switch section.type {
case .common:
print("📘 [기본 정보]")
section.item.forEach { item in
if case let .common(title, value) = item {
print(" - \(title): \(value ?? "없음")")
}
}
case .intro:
print("📗 [소개]")
section.item.forEach { item in
if case let .intro(title, value) = item {
print(" - \(title): \(value ?? "없음")")
}
}
case .image:
print("📙 [이미지]")
section.item.forEach { item in
if case let .image(title, value) = item {
print(" - \(title): \(value ?? "없음")")
}
}
}
}
}
.store(in: &cancellables)
}
}
'Project > HiddenGem' 카테고리의 다른 글
🤔 뷰 컨트롤러가 모달로 띄워졌는지 어떻게 알 수 있나? (0) | 2025.06.02 |
---|---|
🗺️ 지도 표시하기 (0) | 2025.06.02 |
✅ UICollectionViewCell을 공용으로 사용하려면? (데이터 타입도 다를때) (0) | 2025.05.27 |
🤷 컬렉션뷰에 페이징 기능 추가 (스크롤하면 새 데이터 불러와 컬렉션뷰로 보여주기) (0) | 2025.05.22 |
🔨 화면 UI을 동시에 나오게 해서 사용자 친화적으로 해보기 (0) | 2025.05.19 |