diff --git a/TMON-iOS/TMON-iOS.xcodeproj/project.pbxproj b/TMON-iOS/TMON-iOS.xcodeproj/project.pbxproj index 132a467..ea07be9 100644 --- a/TMON-iOS/TMON-iOS.xcodeproj/project.pbxproj +++ b/TMON-iOS/TMON-iOS.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 4C5064872927930800A575CE /* ProductsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5064862927930800A575CE /* ProductsViewController.swift */; }; + 4C506489292795B300A575CE /* ProductDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C506488292795B300A575CE /* ProductDetailViewController.swift */; }; + 4C8927222930C3AF0046AE93 /* BestProductsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8927212930C3AF0046AE93 /* BestProductsCollectionViewCell.swift */; }; + 4CB29AF3292DB8660057F0C4 /* RecommendedProductModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB29AF2292DB8660057F0C4 /* RecommendedProductModel.swift */; }; + 4CB29AF5292F4BBF0057F0C4 /* RecommendedProductsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB29AF4292F4BBF0057F0C4 /* RecommendedProductsCollectionViewCell.swift */; }; + 4CB29AF729308A770057F0C4 /* BestProductModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB29AF629308A770057F0C4 /* BestProductModel.swift */; }; EA887A5D29248CD100F1E38E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA887A5C29248CD100F1E38E /* AppDelegate.swift */; }; EA887A5F29248CD100F1E38E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA887A5E29248CD100F1E38E /* SceneDelegate.swift */; }; EA887A6629248CD300F1E38E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA887A6529248CD300F1E38E /* Assets.xcassets */; }; @@ -34,6 +40,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 4C5064862927930800A575CE /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = ""; }; + 4C506488292795B300A575CE /* ProductDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetailViewController.swift; sourceTree = ""; }; + 4C8927212930C3AF0046AE93 /* BestProductsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BestProductsCollectionViewCell.swift; sourceTree = ""; }; + 4CB29AF2292DB8660057F0C4 /* RecommendedProductModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedProductModel.swift; sourceTree = ""; }; + 4CB29AF4292F4BBF0057F0C4 /* RecommendedProductsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedProductsCollectionViewCell.swift; sourceTree = ""; }; + 4CB29AF629308A770057F0C4 /* BestProductModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BestProductModel.swift; sourceTree = ""; }; EA887A5929248CD100F1E38E /* TMON-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TMON-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; EA887A5C29248CD100F1E38E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; EA887A5E29248CD100F1E38E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -173,6 +185,10 @@ EA887A80292490E100F1E38E /* UIComponents */ = { isa = PBXGroup; children = ( + 4CB29AF2292DB8660057F0C4 /* RecommendedProductModel.swift */, + 4CB29AF4292F4BBF0057F0C4 /* RecommendedProductsCollectionViewCell.swift */, + 4CB29AF629308A770057F0C4 /* BestProductModel.swift */, + 4C8927212930C3AF0046AE93 /* BestProductsCollectionViewCell.swift */, ); path = UIComponents; sourceTree = ""; @@ -258,6 +274,8 @@ EA8F38842924C6BD00517CD4 /* ProductScene */ = { isa = PBXGroup; children = ( + 4C5064862927930800A575CE /* ProductsViewController.swift */, + 4C506488292795B300A575CE /* ProductDetailViewController.swift */, ); path = ProductScene; sourceTree = ""; @@ -353,12 +371,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4CB29AF5292F4BBF0057F0C4 /* RecommendedProductsCollectionViewCell.swift in Sources */, EA887AA1292498EE00F1E38E /* ColorLiterals.swift in Sources */, EA887A5D29248CD100F1E38E /* AppDelegate.swift in Sources */, EA8F38892924C71D00517CD4 /* CategoryMainViewController.swift in Sources */, EA8F387D2924C0E500517CD4 /* imageLiterals.swift in Sources */, EA8F38802924C4C800517CD4 /* TabBarViewController.swift in Sources */, EA887A5F29248CD100F1E38E /* SceneDelegate.swift in Sources */, + 4CB29AF729308A770057F0C4 /* BestProductModel.swift in Sources */, + 4C5064872927930800A575CE /* ProductsViewController.swift in Sources */, + 4CB29AF3292DB8660057F0C4 /* RecommendedProductModel.swift in Sources */, + 4C8927222930C3AF0046AE93 /* BestProductsCollectionViewCell.swift in Sources */, + 4C506489292795B300A575CE /* ProductDetailViewController.swift in Sources */, EA8F38872924C6DC00517CD4 /* HomeViewController.swift in Sources */, EA8F387B2924BFF900517CD4 /* FontLiterals.swift in Sources */, ); diff --git a/TMON-iOS/TMON-iOS/Global/Literal/FontLiterals.swift b/TMON-iOS/TMON-iOS/Global/Literal/FontLiterals.swift index ccab905..f7f06a4 100644 --- a/TMON-iOS/TMON-iOS/Global/Literal/FontLiterals.swift +++ b/TMON-iOS/TMON-iOS/Global/Literal/FontLiterals.swift @@ -11,7 +11,7 @@ import UIKit enum FontName: String { case suitSemiBold = "SUIT-SemiBold" case suitRegular = "SUIT-Regular" -} +} extension UIFont { static func font(_ style: FontName, ofSize size: CGFloat) -> UIFont { diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_ad.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_ad.imageset/Contents.json new file mode 100644 index 0000000..8def711 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_ad.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "food_img_ad.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_ad.imageset/food_img_ad.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_ad.imageset/food_img_ad.png new file mode 100644 index 0000000..f03157c Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_ad.imageset/food_img_ad.png differ diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_apple.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_apple.imageset/Contents.json new file mode 100644 index 0000000..babe99e --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_apple.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "food_img_apple.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_apple.imageset/food_img_apple.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_apple.imageset/food_img_apple.png new file mode 100644 index 0000000..7bb3980 Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_apple.imageset/food_img_apple.png differ diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_candy.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_candy.imageset/Contents.json new file mode 100644 index 0000000..66dc355 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_candy.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "food_img_candy.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_candy.imageset/food_img_candy.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_candy.imageset/food_img_candy.png new file mode 100644 index 0000000..8fc0242 Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_candy.imageset/food_img_candy.png differ diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chestnut.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chestnut.imageset/Contents.json new file mode 100644 index 0000000..6e36c72 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chestnut.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "food_img_chestnut.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chestnut.imageset/food_img_chestnut.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chestnut.imageset/food_img_chestnut.png new file mode 100644 index 0000000..24c79ac Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chestnut.imageset/food_img_chestnut.png differ diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chocolate.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chocolate.imageset/Contents.json new file mode 100644 index 0000000..7f2b287 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chocolate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "food_img_ chocolate.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chocolate.imageset/food_img_ chocolate.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chocolate.imageset/food_img_ chocolate.png new file mode 100644 index 0000000..cfef4ce Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_chocolate.imageset/food_img_ chocolate.png differ diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_lemon.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_lemon.imageset/Contents.json new file mode 100644 index 0000000..edb69bd --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_lemon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "food_img_lemon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_lemon.imageset/food_img_lemon.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_lemon.imageset/food_img_lemon.png new file mode 100644 index 0000000..c15889e Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_lemon.imageset/food_img_lemon.png differ diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_macaroon.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_macaroon.imageset/Contents.json new file mode 100644 index 0000000..0d8d367 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_macaroon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "food_img_macaroon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_macaroon.imageset/food_img_macaroon.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_macaroon.imageset/food_img_macaroon.png new file mode 100644 index 0000000..13f449c Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_macaroon.imageset/food_img_macaroon.png differ diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_onion.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_onion.imageset/Contents.json new file mode 100644 index 0000000..20de321 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_onion.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "food_img_onion.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_onion.imageset/food_img_onion.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_onion.imageset/food_img_onion.png new file mode 100644 index 0000000..e254440 Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/food_img_onion.imageset/food_img_onion.png differ diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/tmon_btn_down.imageset/Contents.json b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/tmon_btn_down.imageset/Contents.json new file mode 100644 index 0000000..38efce9 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/tmon_btn_down.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "tmon_btn_down.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/tmon_btn_down.imageset/tmon_btn_down.png b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/tmon_btn_down.imageset/tmon_btn_down.png new file mode 100644 index 0000000..a568ef2 Binary files /dev/null and b/TMON-iOS/TMON-iOS/Global/Resource/Assets.xcassets/tmon_btn_down.imageset/tmon_btn_down.png differ diff --git a/TMON-iOS/TMON-iOS/Global/UIComponents/BestProductModel.swift b/TMON-iOS/TMON-iOS/Global/UIComponents/BestProductModel.swift new file mode 100644 index 0000000..99080cc --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/UIComponents/BestProductModel.swift @@ -0,0 +1,15 @@ +// +// BestProductModel.swift +// TMON-iOS +// +// Created by 정윤선 on 2022/11/25. +// + +import Foundation + +struct BestProductModel { + let productRanking: String + let productImage: String + let productName: String + let productPrice: String +} diff --git a/TMON-iOS/TMON-iOS/Global/UIComponents/BestProductsCollectionViewCell.swift b/TMON-iOS/TMON-iOS/Global/UIComponents/BestProductsCollectionViewCell.swift new file mode 100644 index 0000000..216cf75 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/UIComponents/BestProductsCollectionViewCell.swift @@ -0,0 +1,115 @@ +// +// BestProductsCollectionViewCell.swift +// TMON-iOS +// +// Created by 정윤선 on 2022/11/25. +// + +import UIKit + +import SnapKit +import SwiftyColor + +// MARK: - RecommendedProductsCollectionViewCell + +class BestProductsCollectionViewCell: UICollectionViewCell { + + // MARK: - Identifier + + static let identifier = "BestProductsCollectionViewCell" + + + // MARK: - UI Components + + private let productImageContainerView = UIView() + + private let productImageView = UIImageView() + + private let productRankingLabel: UILabel = { + let label = UILabel() + label.textColor = .mainColor + label.font = UIFont.font(.suitSemiBold, ofSize: 16) + return label + }() + + private let productNameLabel: UILabel = { + let label = UILabel() + label.textColor = .grayColor1 + label.font = UIFont.font(FontName.suitRegular, ofSize: 14) + return label + }() + + private let productPriceLabel: UILabel = { + let label = UILabel() + label.textColor = .grayColor1 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 16) + return label + }() + + + // MARK: - Life Cycles + + override init(frame: CGRect) { + super.init(frame: frame) + layout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension BestProductsCollectionViewCell { + + // MARK: - Layout Helpers + + private func layout() { + backgroundColor = .clear + contentView.backgroundColor = .clear + + // MARK: - addSubview + + [productRankingLabel, productImageContainerView, productNameLabel, productPriceLabel].forEach { + contentView.addSubview($0) + } + productImageContainerView.addSubview(productImageView) + + // MARK: - AutoLayout + + productRankingLabel.snp.makeConstraints { + $0.top.leading.equalToSuperview() + $0.height.equalTo(22) + } + + productImageContainerView.snp.makeConstraints { + $0.top.equalTo(productRankingLabel.snp.bottom).offset(5) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(176) + } + + productImageView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + productNameLabel.snp.makeConstraints { + $0.top.equalTo(self.productImageContainerView.snp.bottom).offset(8) + $0.leading.equalToSuperview() + } + + productPriceLabel.snp.makeConstraints { + $0.top.equalTo(self.productNameLabel.snp.bottom).offset(1) + $0.leading.equalToSuperview() + } + + } + + + // MARK: - General Helpers + + func dataBind(model: BestProductModel) { + productRankingLabel.text = model.productRanking + productNameLabel.text = model.productName + productPriceLabel.text = model.productPrice + productImageView.image = UIImage(named: model.productImage) + } +} diff --git a/TMON-iOS/TMON-iOS/Global/UIComponents/RecommendedProductModel.swift b/TMON-iOS/TMON-iOS/Global/UIComponents/RecommendedProductModel.swift new file mode 100644 index 0000000..a0300b7 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/UIComponents/RecommendedProductModel.swift @@ -0,0 +1,14 @@ +// +// ProductModel.swift +// TMON-iOS +// +// Created by 정윤선 on 2022/11/23. +// + +import Foundation + +struct RecommendedProductModel { + let productImage: String + let productName: String + let productPrice: String +} diff --git a/TMON-iOS/TMON-iOS/Global/UIComponents/RecommendedProductsCollectionViewCell.swift b/TMON-iOS/TMON-iOS/Global/UIComponents/RecommendedProductsCollectionViewCell.swift new file mode 100644 index 0000000..ed5230f --- /dev/null +++ b/TMON-iOS/TMON-iOS/Global/UIComponents/RecommendedProductsCollectionViewCell.swift @@ -0,0 +1,104 @@ +// +// RecommendedProductsCollectionViewCell.swift +// TMON-iOS +// +// Created by 정윤선 on 2022/11/24. +// + +import UIKit + +import SnapKit +import SwiftyColor + +// MARK: - RecommendedProductsCollectionViewCell + +class RecommendedProductsCollectionViewCell: UICollectionViewCell { + + // MARK: - Identifier + + static let identifier = "RecommendedProductsCollectionViewCell" + + + // MARK: - UI Components + + private let productImageContainerView = UIView() + + private let productImageView = UIImageView() + + private let productNameLabel: UILabel = { + let label = UILabel() + label.textColor = .grayColor1 + label.font = UIFont.font(FontName.suitRegular, ofSize: 12) + label.numberOfLines = 2 + return label + }() + + private let productPriceLabel: UILabel = { + let label = UILabel() + label.textColor = .grayColor1 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 14) + return label + }() + + + // MARK: - Life Cycles + + override init(frame: CGRect) { + super.init(frame: frame) + layout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension RecommendedProductsCollectionViewCell { + + // MARK: - Layout Helpers + + private func layout() { + backgroundColor = .clear + contentView.backgroundColor = .clear + + // MARK: - addSubview + + [productImageContainerView, productNameLabel, productPriceLabel].forEach { + contentView.addSubview($0) + } + productImageContainerView.addSubview(productImageView) + + // MARK: - AutoLayout + + productImageContainerView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(134) + } + + productImageView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + productNameLabel.snp.makeConstraints { + $0.top.equalTo(self.productImageContainerView.snp.bottom).offset(10) + $0.leading.equalToSuperview() + $0.width.equalTo(125) + } + + productPriceLabel.snp.makeConstraints { + $0.top.equalTo(self.productNameLabel.snp.bottom).offset(10) + $0.leading.equalToSuperview() + $0.width.equalTo(125) + } + + } + + + // MARK: - General Helpers + + func dataBind(model: RecommendedProductModel) { + productNameLabel.text = model.productName + productPriceLabel.text = model.productPrice + productImageView.image = UIImage(named: model.productImage) + } +} diff --git a/TMON-iOS/TMON-iOS/Info.plist b/TMON-iOS/TMON-iOS/Info.plist index 0eb786d..9ab6711 100644 --- a/TMON-iOS/TMON-iOS/Info.plist +++ b/TMON-iOS/TMON-iOS/Info.plist @@ -2,6 +2,11 @@ + UIAppFonts + + SUIT-SemiBold.ttf + SUIT-Regular.ttf + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/TMON-iOS/TMON-iOS/Screens/CategoryScene/ProductScene/ProductDetailViewController.swift b/TMON-iOS/TMON-iOS/Screens/CategoryScene/ProductScene/ProductDetailViewController.swift new file mode 100644 index 0000000..8f67936 --- /dev/null +++ b/TMON-iOS/TMON-iOS/Screens/CategoryScene/ProductScene/ProductDetailViewController.swift @@ -0,0 +1,29 @@ +// +// ProductDetailViewController.swift +// TMON-iOS +// +// Created by 정윤선 on 2022/11/18. +// + +import UIKit + +class ProductDetailViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + /* + // 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. + } + */ + +} diff --git a/TMON-iOS/TMON-iOS/Screens/CategoryScene/ProductScene/ProductsViewController.swift b/TMON-iOS/TMON-iOS/Screens/CategoryScene/ProductScene/ProductsViewController.swift new file mode 100644 index 0000000..e618e6e --- /dev/null +++ b/TMON-iOS/TMON-iOS/Screens/CategoryScene/ProductScene/ProductsViewController.swift @@ -0,0 +1,602 @@ +// +// ProductsViewController.swift +// TMON-iOS +// +// Created by 정윤선 on 2022/11/18. +// + +import UIKit + +import SnapKit +import SwiftyColor + + +// MARK: - ProductsViewController + +class ProductsViewController: UIViewController { + + // MARK: - UI Components + + // MARK: - navigationView UI Components + + private let navigationView: UIView = { + let view = UIView() + view.backgroundColor = .white + return view + }() + + private let topNavigationView = UIView() + + private let backButton = UIButton() + private let backImageContainerView = UIView() + private let backImageView = UIImageView() + + private let foodLabel: UILabel = { + let label = UILabel() + label.text = "식품" + label.textColor = .grayColor1 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 18) + return label + }() + + private let alarmButton = UIButton() + private let alarmImageContainerView = UIView() + private let alarmImageView = UIImageView() + + private let cartButton = UIButton() + private let cartImageContainerView = UIView() + private let cartImageView = UIImageView() + + private let bottomNavigationView = UIView() + + private let entireFoodLabel: UILabel = { + let label = UILabel() + label.text = "전체" + label.textColor = .mainColor + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 14) + return label + }() + private let entireFoodLabelUnderlineView: UIView = { + let view = UIView() + view.backgroundColor = .mainColor + return view + }() + + private let freshFoodLabel: UILabel = { + let label = UILabel() + label.text = "신선식품" + label.textColor = .grayColor3 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 14) + return label + }() + + private let processedFoodLabel: UILabel = { + let label = UILabel() + label.text = "가공식품" + label.textColor = .grayColor3 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 14) + return label + }() + + private let healthAndDietFoodLabel: UILabel = { + let label = UILabel() + label.text = "건강식품/다이어트" + label.textColor = .grayColor3 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 14) + return label + }() + + private let coffeeAndDrinksFoodLabel: UILabel = { + let label = UILabel() + label.text = "커피/음료" + label.textColor = .grayColor3 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 14) + return label + }() + + // MARK: - productsScrollView UI Components + + private lazy var productsScrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.backgroundColor = .white + scrollView.showsVerticalScrollIndicator = false + return scrollView + }() + + private let bannerImageContainerView = UIView() + private let bannerImageView = UIImageView() + + // MARK: - recommendedProductsView + + private let recommendedProductsHeaderView = UIView() + + private let recommendedProductsLabel: UILabel = { + let label = UILabel() + label.text = "맞춤 상품 추천" + label.textColor = .grayColor1 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 18) + return label + }() + + private let goToRecommendedProductsPageButton = UIButton() + private let goToRecommendedProductsPageImageContainerView = UIView() + private let goToRecommendedProductsPageImageView = UIImageView() + + private lazy var recommendedProductsCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .clear + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.delegate = self + collectionView.dataSource = self + return collectionView + }() + + private let borderView: UIView = { + let view = UIView() + view.backgroundColor = .grayColor4 + return view + }() + + // MARK: - bestProductsView + + private let bestProductsHeaderView = UIView() + + private let bestProductsLabel: UILabel = { + let label = UILabel() + label.text = "베스트 상품" + label.textColor = .grayColor1 + label.font = UIFont.font(FontName.suitSemiBold, ofSize: 18) + return label + }() + + private let goToBestProductsPageButton = UIButton() + private let goToBestProductsPageImageContainerView = UIView() + private let goToBestProductsPageImageView = UIImageView() + + private lazy var bestProductsCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .clear + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.isScrollEnabled = false + collectionView.showsVerticalScrollIndicator = false + collectionView.delegate = self + collectionView.dataSource = self + return collectionView + }() + + private let seeMoreBestProductsButton: UIButton = { + let button = UIButton() + button.setTitle("베스트 상품 더보기 ", for: .normal) + button.setTitleColor(.grayColor2, for: .normal) + button.titleLabel?.font = UIFont.font(.suitRegular, ofSize: 14) + /* + let imageConfig = UIImage.SymbolConfiguration(pointSize: 20, weight: .light) + let image = UIImage(systemName: "tmon_btn_down", withConfiguration: imageConfig) + button.setImage(image, for: .normal) + button.semanticContentAttribute = .forceRightToLeft + */ + button.layer.cornerRadius = 20 + button.layer.borderWidth = 1 + button.layer.borderColor = UIColor.grayColor2.cgColor + return button + }() + + private let temporaryFooter = UIView() + + + // MARK: - Variables + + var recommendedProductsList: [RecommendedProductModel] = [ + RecommendedProductModel(productImage: "food_img_macaroon", productName: "[스낵위크]롯데제과 마카롱 / 60%할인", productPrice: "19,900원~"), + RecommendedProductModel(productImage: "food_img_candy", productName: "[스낵위크] 롯데제과 캔디 묶음 상품 / 40%할인", productPrice: "7,000원~"), + RecommendedProductModel(productImage: "food_img_chocolate", productName: "[스낵위크] 롯데제과 수제 초콜릿 / 40%할인", productPrice: "26,400원~") + ] + + var bestProductsList: [BestProductModel] = [ + BestProductModel(productRanking: "01", productImage: "food_img_apple", productName: "산지 직송 안동 햇사과 5kg", productPrice: "24,900원"), + BestProductModel(productRanking: "02", productImage: "food_img_onion", productName: "국내산 무안 자색양파 미니 5kg", productPrice: "24% 7,900원"), + BestProductModel(productRanking: "03", productImage: "food_img_chestnut", productName: "충남 공주 정안 옥광밤 4kg", productPrice: "48,900원"), + BestProductModel(productRanking: "04", productImage: "food_img_lemon", productName: "무농약 제주 청레몬 450g (3...", productPrice: "10,900원") + ] + + + // MARK: - Constants + + final let recommendedProductCellHeight: CGFloat = 205 + final let recommendedProductLineSpacing: CGFloat = 9 + final let recommendedProductInset: UIEdgeInsets = UIEdgeInsets(top: 19, left: 16, bottom: 26, right: 16) + + final let bestProductCellHeight: CGFloat = 258 + final let bestProductLineSpacing: CGFloat = 20 + final let bestProductInterItemSpacing: CGFloat = 10 + final let bestProductInset: UIEdgeInsets = UIEdgeInsets(top: 15, left: 16, bottom: 16, right: 16) + + + // MARK: - Life Cycles + + override func viewDidLoad() { + super.viewDidLoad() + configImageView() + register() + layout() + } +} + + +// MARK: - Extensions + +extension ProductsViewController { + + // MARK: - Layout Helpers + + private func layout() { + view.backgroundColor = .white + + // MARK: - addSubview + + [navigationView, productsScrollView].forEach { + view.addSubview($0) + } + + [topNavigationView, bottomNavigationView].forEach { + navigationView.addSubview($0) + } + + [backButton, backImageContainerView, foodLabel, alarmButton, alarmImageContainerView, cartButton, cartImageContainerView].forEach { + topNavigationView.addSubview($0) + } + + backImageContainerView.addSubview(backImageView) + alarmImageContainerView.addSubview(alarmImageView) + cartImageContainerView.addSubview(cartImageView) + + [entireFoodLabel, entireFoodLabelUnderlineView, freshFoodLabel, processedFoodLabel, healthAndDietFoodLabel, coffeeAndDrinksFoodLabel].forEach { + bottomNavigationView.addSubview($0) + } + + [bannerImageContainerView, recommendedProductsHeaderView, recommendedProductsCollectionView, borderView, bestProductsHeaderView, bestProductsCollectionView, seeMoreBestProductsButton, temporaryFooter].forEach { + productsScrollView.addSubview($0) + } + + bannerImageContainerView.addSubview(bannerImageView) + + [recommendedProductsLabel, goToRecommendedProductsPageButton, goToRecommendedProductsPageImageContainerView].forEach { + recommendedProductsHeaderView.addSubview($0) + } + + goToRecommendedProductsPageImageContainerView.addSubview(goToRecommendedProductsPageImageView) + + [bestProductsLabel, goToBestProductsPageButton, goToBestProductsPageImageContainerView].forEach { + bestProductsHeaderView.addSubview($0) + } + + goToBestProductsPageImageContainerView.addSubview(goToBestProductsPageImageView) + + + // MARK: - NavigationView layout + + navigationView.snp.makeConstraints { + $0.top.leading.trailing.equalTo(self.view.safeAreaLayoutGuide) + $0.height.equalTo(93) + } + + topNavigationView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(63) + } + + backButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(4) + $0.leading.equalToSuperview().offset(16) + $0.width.equalTo(38) + $0.height.equalTo(36) + } + + backImageContainerView.snp.makeConstraints { + $0.top.equalToSuperview().offset(4) + $0.leading.equalToSuperview().offset(16) + $0.width.equalTo(38) + $0.height.equalTo(36) + } + + backImageView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + foodLabel.snp.makeConstraints { + $0.top.equalToSuperview().offset(11) + $0.centerX.equalToSuperview() + } + + alarmButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(8) + $0.trailing.equalTo(self.cartButton.snp.leading) + $0.width.equalTo(28) + $0.height.equalTo(28) + } + + alarmImageContainerView.snp.makeConstraints { + $0.top.equalToSuperview().offset(8) + $0.trailing.equalTo(self.cartButton.snp.leading) + $0.width.equalTo(28) + $0.height.equalTo(28) + } + + alarmImageView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + cartButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(8) + $0.trailing.equalToSuperview().inset(16) + $0.width.equalTo(28) + $0.height.equalTo(28) + } + + cartImageContainerView.snp.makeConstraints { + $0.top.equalToSuperview().offset(8) + $0.trailing.equalToSuperview().inset(16) + $0.width.equalTo(28) + $0.height.equalTo(28) + } + + cartImageView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + bottomNavigationView.snp.makeConstraints { + $0.bottom.leading.trailing.equalToSuperview() + $0.height.equalTo(30) + } + + entireFoodLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(33) + } + + entireFoodLabelUnderlineView.snp.makeConstraints { + $0.centerX.equalTo(self.entireFoodLabel.snp.centerX) + $0.bottom.equalToSuperview() + $0.width.equalTo(44) + $0.height.equalTo(2) + } + + freshFoodLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(self.entireFoodLabel.snp.trailing).offset(27) + } + + processedFoodLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(self.freshFoodLabel.snp.trailing).offset(18) + } + + healthAndDietFoodLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(self.processedFoodLabel.snp.trailing).offset(18) + } + + coffeeAndDrinksFoodLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalTo(self.healthAndDietFoodLabel.snp.trailing).offset(18) + } + + // MARK: - productsScrollView layout + + // ScrollView의 마지막 subview에 $0.bottom.equalToSuperview().offset(-45) 주는거 잊지말기! + + productsScrollView.snp.makeConstraints { + $0.top.equalTo(self.navigationView.snp.bottom) + $0.leading.trailing.bottom.equalTo(self.view.safeAreaLayoutGuide) + } + + bannerImageContainerView.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + // width 안줘도 되어야 하는데... 왜 안되지? + $0.width.equalTo(394) + $0.height.equalTo(116) + } + + bannerImageView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + // MARK: - RecommendedProductsView layout + + recommendedProductsHeaderView.snp.makeConstraints { + $0.top.equalTo(self.bannerImageView.snp.bottom).offset(31) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(22) + } + + recommendedProductsLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(16) + $0.centerY.equalToSuperview() + } + + goToRecommendedProductsPageButton.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(16) + $0.centerY.equalToSuperview() + $0.width.equalTo(22) + $0.height.equalTo(22) + } + + goToRecommendedProductsPageImageContainerView.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(16) + $0.centerY.equalToSuperview() + $0.width.equalTo(22) + $0.height.equalTo(22) + } + + goToRecommendedProductsPageImageView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + recommendedProductsCollectionView.snp.makeConstraints { + $0.top.equalTo(self.recommendedProductsHeaderView.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(recommendedProductCellHeight + recommendedProductInset.top + recommendedProductInset.bottom) + } + + borderView.snp.makeConstraints { + $0.top.equalTo(self.recommendedProductsCollectionView.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(8) + } + + // MARK: - BestProductsView Layout + + bestProductsHeaderView.snp.makeConstraints { + $0.top.equalTo(self.borderView.snp.bottom).offset(31) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(22) + } + + bestProductsLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(16) + $0.centerY.equalToSuperview() + } + + goToBestProductsPageButton.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(16) + $0.centerY.equalToSuperview() + $0.width.equalTo(22) + $0.height.equalTo(22) + } + + goToBestProductsPageImageContainerView.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(16) + $0.centerY.equalToSuperview() + $0.width.equalTo(22) + $0.height.equalTo(22) + } + + goToBestProductsPageImageView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + bestProductsCollectionView.snp.makeConstraints { + $0.top.equalTo(self.bestProductsHeaderView.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(calculateBestProductsCollectionViewHeight()) + } + + seeMoreBestProductsButton.snp.makeConstraints { + $0.top.equalTo(self.bestProductsCollectionView.snp.bottom) + $0.centerX.equalToSuperview() + $0.width.equalTo(156) + $0.height.equalTo(40) + } + + temporaryFooter.snp.makeConstraints { + $0.top.equalTo(self.seeMoreBestProductsButton.snp.bottom).offset(16) + $0.leading.trailing.equalToSuperview() + $0.bottom.equalToSuperview().offset(-50) + } + } + + + // MARK: - General Helpers + + private func configImageView() { + backImageView.image = UIImage(named: "tmon_ios_btn_back") + alarmImageView.image = UIImage(named: "tmon_btn_alarm") + cartImageView.image = UIImage(named: "tmon_btn_shopping") + bannerImageView.image = UIImage(named: "food_img_ad") + goToRecommendedProductsPageImageView.image = UIImage(named: "tmon_btn_more") + goToBestProductsPageImageView.image = UIImage(named: "tmon_btn_more") + } + + private func register() { + recommendedProductsCollectionView.register(RecommendedProductsCollectionViewCell.self, forCellWithReuseIdentifier: RecommendedProductsCollectionViewCell.identifier) + bestProductsCollectionView.register(BestProductsCollectionViewCell.self, forCellWithReuseIdentifier: BestProductsCollectionViewCell.identifier) + } + + private func calculateBestProductsCollectionViewHeight() -> CGFloat { + let count = CGFloat(bestProductsList.count) + let heightCount = count / 2 + count.truncatingRemainder(dividingBy: 2) + return (heightCount * bestProductCellHeight) + ((heightCount - 1) * bestProductLineSpacing) + bestProductInset.top + bestProductInset.bottom + } +} + + +// MARK: - UICollectionViewDelegateFlowLayout + +extension ProductsViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + if (collectionView == recommendedProductsCollectionView) { + return CGSize(width: 134, height: 205) + } else if (collectionView == bestProductsCollectionView) { + let screenWidth = UIScreen.main.bounds.width + let doubleCellWidth = screenWidth - bestProductInset.left - bestProductInset.right - bestProductInterItemSpacing + return CGSize(width: doubleCellWidth / 2, height: 258) + } else { + return CGSize(width: 100, height: 100) + } + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + if (collectionView == recommendedProductsCollectionView) { + return recommendedProductLineSpacing + } else if (collectionView == bestProductsCollectionView) { + return bestProductLineSpacing + } else { + return 0 + } + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + if (collectionView == bestProductsCollectionView) { + return bestProductInterItemSpacing + } else { + return 0 + } + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + if (collectionView == recommendedProductsCollectionView) { + return recommendedProductInset + } else if (collectionView == bestProductsCollectionView) { + return bestProductInset + } else { + return UIEdgeInsets() + } + } +} + + +// MARK: - UICollectionViewDataSource + +extension ProductsViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + if (collectionView == recommendedProductsCollectionView) { + return recommendedProductsList.count + } else if (collectionView == bestProductsCollectionView) { + return bestProductsList.count + } else { + return 0 + } + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + if (collectionView == recommendedProductsCollectionView) { + guard let recommendedProductCell = collectionView.dequeueReusableCell(withReuseIdentifier: "RecommendedProductsCollectionViewCell", for: indexPath) + as? RecommendedProductsCollectionViewCell else { return UICollectionViewCell() } + recommendedProductCell.dataBind(model: recommendedProductsList[indexPath.item]) + return recommendedProductCell + } else if (collectionView == bestProductsCollectionView) { + guard let bestProductCell = collectionView.dequeueReusableCell(withReuseIdentifier: "BestProductsCollectionViewCell", for: indexPath) as? BestProductsCollectionViewCell else { return UICollectionViewCell() } + bestProductCell.dataBind(model: bestProductsList[indexPath.item]) + return bestProductCell + } else { + return UICollectionViewCell() + } + } +}