From 7cf0ddc5698eb77cd81a240334e5cbef541e6bfd Mon Sep 17 00:00:00 2001 From: Jisoo Kim Date: Thu, 22 Apr 2021 14:02:11 +0900 Subject: [PATCH] feat: Mod BanchanListViewMdoel, BanchanListViewController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DiffableDataSource를 이용한 Section Update 가능하도록 구현하였습니다. Notification을 이용하였지만 Combine으로 구현해볼 예정입니다. issue: #13, #16 --- .../SideDish.xcodeproj/project.pbxproj | 8 +- .../UserInterfaceState.xcuserstate | Bin 24136 -> 39076 bytes .../DataMapping/BanchanListDTO+Mapping.swift | 8 +- .../Data/Network/NetworkService.swift | 5 ++ .../SideDish/Domain/Entities/Banchan.swift | 6 +- .../UseCases/FetchBanchanListUseCase.swift | 7 +- iOS/SideDish/SideDish/Info.plist | 5 ++ .../ViewModel/BanchanListViewModel.swift | 69 ++++++++++++------ .../BanchanList/ViewModel/Section.swift | 40 ---------- .../ViewModel/View/BanchanCustomCell.swift | 16 ++-- .../View/BanchanListViewController.swift | 44 +++++------ 11 files changed, 98 insertions(+), 110 deletions(-) delete mode 100644 iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/Section.swift diff --git a/iOS/SideDish/SideDish.xcodeproj/project.pbxproj b/iOS/SideDish/SideDish.xcodeproj/project.pbxproj index 189cf9dfe..dc208b87b 100644 --- a/iOS/SideDish/SideDish.xcodeproj/project.pbxproj +++ b/iOS/SideDish/SideDish.xcodeproj/project.pbxproj @@ -26,12 +26,12 @@ D4BFBB36262EB89800D68297 /* BanchanCustomCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D4BFBB34262EB89800D68297 /* BanchanCustomCell.xib */; }; D4BFBB3C262EC8DB00D68297 /* BanchanCustomCellHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4BFBB3A262EC8DB00D68297 /* BanchanCustomCellHeader.swift */; }; D4BFBB3D262EC8DB00D68297 /* BanchanCustomCellHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = D4BFBB3B262EC8DB00D68297 /* BanchanCustomCellHeader.xib */; }; - D4BFBB5626301B6B00D68297 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4BFBB5526301B6B00D68297 /* Section.swift */; }; FFA10EB1262FC6CD00D584B6 /* BanchanCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA10EB0262FC6CD00D584B6 /* BanchanCollectionView.swift */; }; FFEF70F926310FB400189376 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF70F826310FB400189376 /* NetworkService.swift */; }; FFEF70FF2631104B00189376 /* BanchanListDTO+Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF70FE2631104B00189376 /* BanchanListDTO+Mapping.swift */; }; FFEF71042631107200189376 /* FetchBanchanListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF71032631107200189376 /* FetchBanchanListUseCase.swift */; }; FFEF71092631128900189376 /* FetchImageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF71082631128900189376 /* FetchImageUseCase.swift */; }; + FFEF7123263119C500189376 /* BanchanListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7122263119C500189376 /* BanchanListViewModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -81,7 +81,6 @@ D4BFBB34262EB89800D68297 /* BanchanCustomCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BanchanCustomCell.xib; sourceTree = ""; }; D4BFBB3A262EC8DB00D68297 /* BanchanCustomCellHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BanchanCustomCellHeader.swift; sourceTree = ""; }; D4BFBB3B262EC8DB00D68297 /* BanchanCustomCellHeader.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BanchanCustomCellHeader.xib; sourceTree = ""; }; - D4BFBB5526301B6B00D68297 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; D7433C6120AE2C2B5B290532 /* Pods_SideDishTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SideDishTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E5121CE39396F4ECB1937C15 /* Pods-SideDish-SideDishUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SideDish-SideDishUITests.debug.xcconfig"; path = "Target Support Files/Pods-SideDish-SideDishUITests/Pods-SideDish-SideDishUITests.debug.xcconfig"; sourceTree = ""; }; FFA10EB0262FC6CD00D584B6 /* BanchanCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BanchanCollectionView.swift; sourceTree = ""; }; @@ -89,6 +88,7 @@ FFEF70FE2631104B00189376 /* BanchanListDTO+Mapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BanchanListDTO+Mapping.swift"; sourceTree = ""; }; FFEF71032631107200189376 /* FetchBanchanListUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchBanchanListUseCase.swift; sourceTree = ""; }; FFEF71082631128900189376 /* FetchImageUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchImageUseCase.swift; sourceTree = ""; }; + FFEF7122263119C500189376 /* BanchanListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BanchanListViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -255,7 +255,7 @@ D4BFBB21262EA7BC00D68297 /* ViewModel */ = { isa = PBXGroup; children = ( - D4BFBB5526301B6B00D68297 /* Section.swift */, + FFEF7122263119C500189376 /* BanchanListViewModel.swift */, D4BFBB20262EA7B600D68297 /* View */, ); path = ViewModel; @@ -575,7 +575,6 @@ D4BFBAD7262E989000D68297 /* BanchanListViewController.swift in Sources */, D4BFBAD3262E989000D68297 /* AppDelegate.swift in Sources */, D4BFBAD5262E989000D68297 /* SceneDelegate.swift in Sources */, - D4BFBB5626301B6B00D68297 /* Section.swift in Sources */, FFA10EB1262FC6CD00D584B6 /* BanchanCollectionView.swift in Sources */, D4BFBB0A262E9A8200D68297 /* DetailBanchanViewController.swift in Sources */, D4BFBB2F262EAD1C00D68297 /* DetailBanchan.swift in Sources */, @@ -584,6 +583,7 @@ D4BFBB35262EB89800D68297 /* BanchanCustomCell.swift in Sources */, D4BFBB2A262EA93700D68297 /* Banchan.swift in Sources */, FFEF71092631128900189376 /* FetchImageUseCase.swift in Sources */, + FFEF7123263119C500189376 /* BanchanListViewModel.swift in Sources */, D4BFBB3C262EC8DB00D68297 /* BanchanCustomCellHeader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iOS/SideDish/SideDish.xcworkspace/xcuserdata/jibook.xcuserdatad/UserInterfaceState.xcuserstate b/iOS/SideDish/SideDish.xcworkspace/xcuserdata/jibook.xcuserdatad/UserInterfaceState.xcuserstate index 7d00cc44722447ed90999b572c18259c33fe3470..14146f759849b4aba4b75dec04be07a57c2bd232 100644 GIT binary patch delta 22140 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 X-?d3!^@LFWQ?eNU4W7RLCguD;tJ~z{ delta 10193 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 Banchan { - return .init(hash: hash, image: image, alt: alt, title: title, description: description, netPrice: normalPrice, salePrice: salePrice, badge: badge, deliveryType: deliveryType) + var newData: Data? + FetchImageUseCase.fetch(network: NetworkSerivce.shared, imgURL: image) { data in + DispatchQueue.main.async { + newData = data + } + } + return .init(hash: hash, image: newData, alt: alt, title: title, description: description, netPrice: normalPrice, salePrice: salePrice, badge: badge, delivery_type: deliveryType) } } diff --git a/iOS/SideDish/SideDish/Data/Network/NetworkService.swift b/iOS/SideDish/SideDish/Data/Network/NetworkService.swift index bc4c8b786..d032e8c98 100644 --- a/iOS/SideDish/SideDish/Data/Network/NetworkService.swift +++ b/iOS/SideDish/SideDish/Data/Network/NetworkService.swift @@ -14,6 +14,11 @@ protocol NetworkRequest { class NetworkSerivce: NetworkRequest { + static let shared = NetworkSerivce() + + private init() { + } + func request(url strUrl: String, httpMethod: HTTPMethod, completion: @escaping (AFDataResponse) -> Void) { guard let url = URL(string: strUrl) else { return } diff --git a/iOS/SideDish/SideDish/Domain/Entities/Banchan.swift b/iOS/SideDish/SideDish/Domain/Entities/Banchan.swift index 0fa58821b..9ad7f751b 100644 --- a/iOS/SideDish/SideDish/Domain/Entities/Banchan.swift +++ b/iOS/SideDish/SideDish/Domain/Entities/Banchan.swift @@ -19,14 +19,14 @@ enum PriceType { struct Banchan: Hashable { private (set) var hash: String - private (set) var image: String + private (set) var image: Data? private (set) var alt: String private (set) var title: String private (set) var description: String private (set) var netPrice: String? private (set) var salePrice: String - private (set) var badge: [PriceType] - private (set) var delivery_type: [Delivery] + private (set) var badge: [String]? + private (set) var delivery_type: [String] func hash(into hasher: inout Hasher) { hasher.combine(hash) diff --git a/iOS/SideDish/SideDish/Domain/UseCases/FetchBanchanListUseCase.swift b/iOS/SideDish/SideDish/Domain/UseCases/FetchBanchanListUseCase.swift index 44de4ced6..9a687aa01 100644 --- a/iOS/SideDish/SideDish/Domain/UseCases/FetchBanchanListUseCase.swift +++ b/iOS/SideDish/SideDish/Domain/UseCases/FetchBanchanListUseCase.swift @@ -8,10 +8,11 @@ import Foundation struct FetchBanchanListUseCase { - private static let url: String = "https://h3rb9c0ugl.execute-api.ap-northeast-2.amazonaws.com/develop/baminchan/side" + private static let baseURL: String = "https://h3rb9c0ugl.execute-api.ap-northeast-2.amazonaws.com/develop/baminchan/" - static func fetchBanchanList(network: NetworkRequest, completion: @escaping ([Banchan]?) -> Void) { - network.request(url: self.url, httpMethod: .get) { dataDummy in + static func fetchBanchanList(network: NetworkRequest,section: String, completion: @escaping ([Banchan]?) -> Void) { + let url = baseURL+section + network.request(url: url, httpMethod: .get) { dataDummy in guard let data = dataDummy.data else { return } guard let banchans = try? JSONDecoder().decode(BanchanListDTO.self, from: data) else { return } diff --git a/iOS/SideDish/SideDish/Info.plist b/iOS/SideDish/SideDish/Info.plist index 5b531f7b2..53c6efd04 100644 --- a/iOS/SideDish/SideDish/Info.plist +++ b/iOS/SideDish/SideDish/Info.plist @@ -2,6 +2,11 @@ + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/BanchanListViewModel.swift b/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/BanchanListViewModel.swift index 577363082..edd048c72 100644 --- a/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/BanchanListViewModel.swift +++ b/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/BanchanListViewModel.swift @@ -2,30 +2,53 @@ // BanchanListViewModel.swift // SideDish // -// Created by 지북 on 2021/04/20. +// Created by 지북 on 2021/04/22. // import Foundation -import UIKit -//class BanchanListViewModel { -//// enum Section: String, CaseIterable { -//// case soup = "정성이 담긴 뜨끈뜨끈 국물요리" -//// case side = "식탁을 풍성하게 하는 정갈한 밑반찬" -//// case main = "모두가 좋아하는 든든한 메인요리" -//// } -// -// var menu: Dictionary -// -// init() { -// self.menu = [:] -// } -// -// func sectionCount() -> Int { -// return Section.allCases.count -// } -// -// func getBanchans(section: Section) -> [Banchan]? { -// return menu[section] -// } -//} +class BanchanListViewModel { + enum Section: String, CaseIterable { + case soup = "정성이 담긴 뜨끈뜨끈 국물요리" + case side = "식탁을 풍성하게 하는 정갈한 밑반찬" + case main = "모두가 좋아하는 든든한 메인요리" + } + + var menu: Dictionary { + didSet { + NotificationCenter.default.post(name: Notification.Name("updateMenu"), object: self) + } + } + var network = NetworkSerivce.shared + + init() { + self.menu = [:] + fetchMenu() + } + + func fetchMenu() { + FetchBanchanListUseCase.fetchBanchanList(network: network, section: "main", completion: { banchans in + guard let banchans = banchans else { return } + self.menu[.main] = banchans + }) + + FetchBanchanListUseCase.fetchBanchanList(network: network, section: "soup", completion: { banchans in + guard let banchans = banchans else { return } + self.menu[.soup] = banchans + }) + + FetchBanchanListUseCase.fetchBanchanList(network: network, section: "side", completion: { banchans in + guard let banchans = banchans else { return } + self.menu[.side] = banchans + }) + } + + func sectionCount() -> Int { + return Section.allCases.count + } + + func getBanchans(section: Section) -> [Banchan]? { + return menu[section] + } +} + diff --git a/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/Section.swift b/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/Section.swift deleted file mode 100644 index ed5346172..000000000 --- a/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/Section.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Section.swift -// SideDish -// -// Created by 심영민 on 2021/04/21. -// - -import UIKit - -class Section: Hashable { - enum SectionTitle: String { - case main = "모두가 좋아하는 든든한 메인요리" - case soup = "정성이 담긴 뜨끈뜨끈 국물요리" - case side = "식탁을 풍성하게 하는 정갈한 밑반찬" - } - var id = UUID() - var banchans: [Banchan] - var title: SectionTitle - - init(title: SectionTitle, banchans: [Banchan]) { - self.title = title - self.banchans = banchans - } - static func == (lhs: Section, rhs: Section) -> Bool { - return lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} - -extension Section { - static var allSections: [Section] = [ - - Section(title: .main, banchans: [Banchan(hash: "1", image: "https://pbs.twimg.com/profile_images/770139154898382848/ndFg-IDH.jpg", alt: "alt", title: "타이틀", description: "데스크립션", netPrice: "1000", salePrice: "1000", badge: [], delivery_type: [])]), - Section(title: .soup, banchans: [Banchan(hash: "2", image: "https://pbs.twimg.com/profile_images/770139154898382848/ndFg-IDH.jpg", alt: "alt", title: "타이틀", description: "데스크립션", netPrice: "1000", salePrice: "1000", badge: [], delivery_type: [])]), - Section(title: .side, banchans: [Banchan(hash: "3", image: "https://pbs.twimg.com/profile_images/770139154898382848/ndFg-IDH.jpg", alt: "alt", title: "타이틀", description: "데스크립션", netPrice: "1000", salePrice: "1000", badge: [], delivery_type: [])]) - ] -} diff --git a/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/View/BanchanCustomCell.swift b/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/View/BanchanCustomCell.swift index a426520a4..26b926c83 100644 --- a/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/View/BanchanCustomCell.swift +++ b/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/View/BanchanCustomCell.swift @@ -8,7 +8,7 @@ import UIKit class BanchanCustomCell: UICollectionViewCell { - + @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var descriptionLabel: UILabel! @@ -28,18 +28,12 @@ class BanchanCustomCell: UICollectionViewCell { var banchan: Banchan? { didSet{ - guard let url = URL(string: "\(banchan!.image)"), - let data = try? Data(contentsOf: url) else { - return - } - - imageView.image = UIImage(data: data) + imageView.image = UIImage(data: banchan!.image ?? Data()) titleLabel.text = banchan?.title descriptionLabel.text = banchan?.description - netPriceLabel.text = "\(banchan?.netPrice ?? "")" - salePriceLabel.text = "\(banchan?.salePrice ?? "")" - priceTypeLabel.text = "\(banchan?.badge ?? [])" + netPriceLabel.text = "1000" + salePriceLabel.text = "2000" + priceTypeLabel.text = "3000" } } - } diff --git a/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/View/BanchanListViewController.swift b/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/View/BanchanListViewController.swift index 9fb62f7ee..b1a5a3315 100644 --- a/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/View/BanchanListViewController.swift +++ b/iOS/SideDish/SideDish/Presentaion/BanchanList/ViewModel/View/BanchanListViewController.swift @@ -11,19 +11,21 @@ class BanchanListViewController: UIViewController { @IBOutlet weak var banchanCollectionView: BanchanCollectionView! - //var viewModel = BanchanListViewModel() - private var sections = Section.allSections + var viewModel = BanchanListViewModel() lazy var dataSource = configureDataSource() typealias DataSource = UICollectionViewDiffableDataSource typealias Snapshot = NSDiffableDataSourceSnapshot + typealias Section = BanchanListViewModel.Section override func viewDidLoad() { super.viewDidLoad() - - applySnapshot(animatingDifferences: false) + + applySnapshot(animatingDifferences: true) banchanCollectionView.dataSource = self.dataSource banchanCollectionView.delegate = self + + NotificationCenter.default.addObserver(self, selector: #selector(applySnapshot(animatingDifferences:)), name: Notification.Name("updateMenu"), object: viewModel) } } @@ -31,40 +33,32 @@ class BanchanListViewController: UIViewController { extension BanchanListViewController { func configureDataSource() -> DataSource { let dataSource = DataSource(collectionView: banchanCollectionView) { (collectionView, indexPath, banchan) -> UICollectionViewCell? in - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BanchanCustomCell.identifer, for: indexPath) as? BanchanCustomCell else { - return nil - } - cell.banchan = banchan - return cell + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BanchanCustomCell.identifer, for: indexPath) as? BanchanCustomCell else { return nil } + cell.banchan = banchan + return cell } - dataSource.supplementaryViewProvider = { (collectionView, kind, indexPath) in - guard kind == UICollectionView.elementKindSectionHeader else { - return nil - } - - guard let view = collectionView.dequeueReusableSupplementaryView( - ofKind: kind, - withReuseIdentifier: BanchanCustomCellHeader.identifier, - for: indexPath) as? BanchanCustomCellHeader else { - return nil - } + guard kind == UICollectionView.elementKindSectionHeader else { return nil } let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section] - view.titleLabel.text = section.title.rawValue + guard let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: BanchanCustomCellHeader.identifier, for: indexPath) as? BanchanCustomCellHeader else { return nil } + view.titleLabel.text = section.rawValue return view } return dataSource } + @objc func applySnapshot(animatingDifferences: Bool = true) { var snapshot = Snapshot() - - snapshot.appendSections(sections) - sections.forEach { section in - snapshot.appendItems(section.banchans, toSection: section) + snapshot.appendSections(Section.allCases) + + Section.allCases.forEach { section in + guard let banchans = viewModel.getBanchans(section: section) else { return } + snapshot.appendItems(banchans, toSection: section) } + dataSource.apply(snapshot, animatingDifferences: animatingDifferences) } }