diff --git a/35-seminar.xcodeproj/project.pbxproj b/35-seminar.xcodeproj/project.pbxproj index fd7a0b9..eed7e36 100644 --- a/35-seminar.xcodeproj/project.pbxproj +++ b/35-seminar.xcodeproj/project.pbxproj @@ -9,6 +9,12 @@ /* Begin PBXBuildFile section */ 152919B72CB10FE200438E2B /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 152919B62CB10FE200438E2B /* SnapKit */; }; 1570C8CD2CB43A4C00A43324 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 1570C8CC2CB43A4C00A43324 /* .gitignore */; }; + 157119D62CBE962F00362252 /* TitleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157119D52CBE962F00362252 /* TitleLabel.swift */; }; + 157119D82CBE965700362252 /* SubtitleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157119D72CBE965700362252 /* SubtitleLabel.swift */; }; + 157119DA2CBE96BB00362252 /* ContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157119D92CBE96BB00362252 /* ContentLabel.swift */; }; + 157119DD2CBE96F500362252 /* UILabel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157119DC2CBE96F500362252 /* UILabel+Extension.swift */; }; + 15728FC52CCCAF0100E1E151 /* Feedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15728FC42CCCAF0100E1E151 /* Feedback.swift */; }; + 15728FC72CCCAF3100E1E151 /* StarColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15728FC62CCCAF3100E1E151 /* StarColor.swift */; }; 1590A63D2CBE6C6A00FB32AE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1590A6352CBE6C6A00FB32AE /* Assets.xcassets */; }; 1590A63F2CBE6C6A00FB32AE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1590A6392CBE6C6A00FB32AE /* LaunchScreen.storyboard */; }; 1590A6402CBE6C6A00FB32AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1590A62A2CBE6C6A00FB32AE /* AppDelegate.swift */; }; @@ -17,11 +23,29 @@ 1590A6432CBE6C6A00FB32AE /* Week1DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1590A62E2CBE6C6A00FB32AE /* Week1DetailViewController.swift */; }; 1590A6442CBE6C6A00FB32AE /* Week1MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1590A6302CBE6C6A00FB32AE /* Week1MainView.swift */; }; 1590A6452CBE6C6A00FB32AE /* Week1MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1590A6312CBE6C6A00FB32AE /* Week1MainViewController.swift */; }; + 1590A6A12CBE737600FB32AE /* PractScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1590A6A02CBE737600FB32AE /* PractScrollViewController.swift */; }; + 1590A6A42CBE8A9200FB32AE /* AppDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1590A6A32CBE8A9200FB32AE /* AppDetailViewController.swift */; }; + 15EC30532CCA434700A0480B /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC30522CCA434700A0480B /* UIButton+Extension.swift */; }; + 15EC30552CCAB69000A0480B /* AppDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC30542CCAB69000A0480B /* AppDetailView.swift */; }; + 15EC30572CCAC33700A0480B /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC30562CCAC33700A0480B /* UIImage+Extension.swift */; }; + 15EC30592CCB727300A0480B /* VersionRecordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC30582CCB727300A0480B /* VersionRecordViewController.swift */; }; + 15EC305B2CCB76F500A0480B /* AllFeedbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC305A2CCB76F500A0480B /* AllFeedbackViewController.swift */; }; + 15EC305D2CCB792300A0480B /* FeedbackWriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC305C2CCB792300A0480B /* FeedbackWriteViewController.swift */; }; + 15EC305F2CCB793500A0480B /* FeedbackWriteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC305E2CCB793500A0480B /* FeedbackWriteView.swift */; }; + 15EC30612CCB8E9C00A0480B /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC30602CCB8E9C00A0480B /* Date+Extension.swift */; }; + 15F4FD072CC73D0A00C99A20 /* StarStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15F4FD062CC73D0A00C99A20 /* StarStackView.swift */; }; + 15F4FD0F2CC7649A00C99A20 /* BorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15F4FD0E2CC7649A00C99A20 /* BorderView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 152919992CB101BA00438E2B /* 35-seminar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "35-seminar.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 1570C8CC2CB43A4C00A43324 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; + 157119D52CBE962F00362252 /* TitleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLabel.swift; sourceTree = ""; }; + 157119D72CBE965700362252 /* SubtitleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleLabel.swift; sourceTree = ""; }; + 157119D92CBE96BB00362252 /* ContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabel.swift; sourceTree = ""; }; + 157119DC2CBE96F500362252 /* UILabel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Extension.swift"; sourceTree = ""; }; + 15728FC42CCCAF0100E1E151 /* Feedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feedback.swift; sourceTree = ""; }; + 15728FC62CCCAF3100E1E151 /* StarColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarColor.swift; sourceTree = ""; }; 1590A62A2CBE6C6A00FB32AE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 1590A62B2CBE6C6A00FB32AE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 1590A62D2CBE6C6A00FB32AE /* Week1DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Week1DetailView.swift; sourceTree = ""; }; @@ -31,6 +55,18 @@ 1590A6352CBE6C6A00FB32AE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1590A6372CBE6C6A00FB32AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1590A6382CBE6C6A00FB32AE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 1590A6A02CBE737600FB32AE /* PractScrollViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PractScrollViewController.swift; sourceTree = ""; }; + 1590A6A32CBE8A9200FB32AE /* AppDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailViewController.swift; sourceTree = ""; }; + 15EC30522CCA434700A0480B /* UIButton+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = ""; }; + 15EC30542CCAB69000A0480B /* AppDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailView.swift; sourceTree = ""; }; + 15EC30562CCAC33700A0480B /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; + 15EC30582CCB727300A0480B /* VersionRecordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionRecordViewController.swift; sourceTree = ""; }; + 15EC305A2CCB76F500A0480B /* AllFeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllFeedbackViewController.swift; sourceTree = ""; }; + 15EC305C2CCB792300A0480B /* FeedbackWriteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackWriteViewController.swift; sourceTree = ""; }; + 15EC305E2CCB793500A0480B /* FeedbackWriteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackWriteView.swift; sourceTree = ""; }; + 15EC30602CCB8E9C00A0480B /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; + 15F4FD062CC73D0A00C99A20 /* StarStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarStackView.swift; sourceTree = ""; }; + 15F4FD0E2CC7649A00C99A20 /* BorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,6 +98,38 @@ name = Products; sourceTree = ""; }; + 157119D42CBE8FB700362252 /* Reusables */ = { + isa = PBXGroup; + children = ( + 15F4FD0E2CC7649A00C99A20 /* BorderView.swift */, + 15F4FD062CC73D0A00C99A20 /* StarStackView.swift */, + 157119D52CBE962F00362252 /* TitleLabel.swift */, + 157119D72CBE965700362252 /* SubtitleLabel.swift */, + 157119D92CBE96BB00362252 /* ContentLabel.swift */, + ); + path = Reusables; + sourceTree = ""; + }; + 157119DB2CBE96DE00362252 /* Extensions */ = { + isa = PBXGroup; + children = ( + 15EC30522CCA434700A0480B /* UIButton+Extension.swift */, + 15EC30562CCAC33700A0480B /* UIImage+Extension.swift */, + 157119DC2CBE96F500362252 /* UILabel+Extension.swift */, + 15EC30602CCB8E9C00A0480B /* Date+Extension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 15728FC32CCCAEE000E1E151 /* Models */ = { + isa = PBXGroup; + children = ( + 15728FC42CCCAF0100E1E151 /* Feedback.swift */, + 15728FC62CCCAF3100E1E151 /* StarColor.swift */, + ); + path = Models; + sourceTree = ""; + }; 1590A62C2CBE6C6A00FB32AE /* Application */ = { isa = PBXGroup; children = ( @@ -103,6 +171,7 @@ children = ( 1590A6392CBE6C6A00FB32AE /* LaunchScreen.storyboard */, 1590A6332CBE6C6A00FB32AE /* Week1 */, + 1590A69F2CBE719000FB32AE /* Week2 */, ); path = Presentation; sourceTree = ""; @@ -126,6 +195,39 @@ path = Resource; sourceTree = ""; }; + 1590A69F2CBE719000FB32AE /* Week2 */ = { + isa = PBXGroup; + children = ( + 15728FC32CCCAEE000E1E151 /* Models */, + 157119DB2CBE96DE00362252 /* Extensions */, + 157119D42CBE8FB700362252 /* Reusables */, + 15F4FD092CC7405800C99A20 /* Practice */, + 15F4FD082CC73E1D00C99A20 /* View */, + ); + path = Week2; + sourceTree = ""; + }; + 15F4FD082CC73E1D00C99A20 /* View */ = { + isa = PBXGroup; + children = ( + 15EC30542CCAB69000A0480B /* AppDetailView.swift */, + 1590A6A32CBE8A9200FB32AE /* AppDetailViewController.swift */, + 15EC305A2CCB76F500A0480B /* AllFeedbackViewController.swift */, + 15EC305E2CCB793500A0480B /* FeedbackWriteView.swift */, + 15EC305C2CCB792300A0480B /* FeedbackWriteViewController.swift */, + 15EC30582CCB727300A0480B /* VersionRecordViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + 15F4FD092CC7405800C99A20 /* Practice */ = { + isa = PBXGroup; + children = ( + 1590A6A02CBE737600FB32AE /* PractScrollViewController.swift */, + ); + path = Practice; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -204,12 +306,30 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 15EC30592CCB727300A0480B /* VersionRecordViewController.swift in Sources */, + 15EC30612CCB8E9C00A0480B /* Date+Extension.swift in Sources */, + 1590A6A12CBE737600FB32AE /* PractScrollViewController.swift in Sources */, + 15728FC72CCCAF3100E1E151 /* StarColor.swift in Sources */, + 157119D82CBE965700362252 /* SubtitleLabel.swift in Sources */, + 15EC30552CCAB69000A0480B /* AppDetailView.swift in Sources */, 1590A6402CBE6C6A00FB32AE /* AppDelegate.swift in Sources */, 1590A6412CBE6C6A00FB32AE /* SceneDelegate.swift in Sources */, + 157119D62CBE962F00362252 /* TitleLabel.swift in Sources */, + 15EC30532CCA434700A0480B /* UIButton+Extension.swift in Sources */, + 1590A6A42CBE8A9200FB32AE /* AppDetailViewController.swift in Sources */, 1590A6422CBE6C6A00FB32AE /* Week1DetailView.swift in Sources */, + 157119DA2CBE96BB00362252 /* ContentLabel.swift in Sources */, 1590A6432CBE6C6A00FB32AE /* Week1DetailViewController.swift in Sources */, 1590A6442CBE6C6A00FB32AE /* Week1MainView.swift in Sources */, + 15EC305D2CCB792300A0480B /* FeedbackWriteViewController.swift in Sources */, + 15F4FD072CC73D0A00C99A20 /* StarStackView.swift in Sources */, + 157119DD2CBE96F500362252 /* UILabel+Extension.swift in Sources */, + 15F4FD0F2CC7649A00C99A20 /* BorderView.swift in Sources */, + 15EC305F2CCB793500A0480B /* FeedbackWriteView.swift in Sources */, + 15EC305B2CCB76F500A0480B /* AllFeedbackViewController.swift in Sources */, + 15EC30572CCAC33700A0480B /* UIImage+Extension.swift in Sources */, 1590A6452CBE6C6A00FB32AE /* Week1MainViewController.swift in Sources */, + 15728FC52CCCAF0100E1E151 /* Feedback.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -241,6 +361,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -268,6 +389,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/35-seminar/Application/SceneDelegate.swift b/35-seminar/Application/SceneDelegate.swift index 879c12b..694f996 100644 --- a/35-seminar/Application/SceneDelegate.swift +++ b/35-seminar/Application/SceneDelegate.swift @@ -14,7 +14,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: windowScene) - let navigationController = UINavigationController(rootViewController: Week1MainViewController()) + let navigationController = UINavigationController(rootViewController: AppDetailViewController()) self.window?.rootViewController = navigationController self.window?.makeKeyAndVisible() } diff --git a/35-seminar/Presentation/Week2/Extensions/Date+Extension.swift b/35-seminar/Presentation/Week2/Extensions/Date+Extension.swift new file mode 100644 index 0000000..ba812d0 --- /dev/null +++ b/35-seminar/Presentation/Week2/Extensions/Date+Extension.swift @@ -0,0 +1,35 @@ +// +// Date+Extension.swift +// 35-seminar +// +// Created by 김유림 on 10/25/24. +// + +import Foundation + +extension Date { + static func form(year: Int, month: Int, day: Int) -> Date? { + var dateComponents = DateComponents() + dateComponents.year = year + dateComponents.month = month + dateComponents.day = day + + return Calendar.current.date(from: dateComponents) + } + + static func formattedDate(date: Date?) -> String { + guard let inputDate = date else { return "날짜 없음" } + + let currentYear = Calendar.current.component(.year, from: Date()) + let inputYear = Calendar.current.component(.year, from: inputDate) + let yearDifference = currentYear - inputYear + + guard yearDifference > 0 else { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "M월 d" + return dateFormatter.string(from: inputDate) + } + + return "\(yearDifference)년 전" + } +} diff --git a/35-seminar/Presentation/Week2/Extensions/UIButton+Extension.swift b/35-seminar/Presentation/Week2/Extensions/UIButton+Extension.swift new file mode 100644 index 0000000..4c79975 --- /dev/null +++ b/35-seminar/Presentation/Week2/Extensions/UIButton+Extension.swift @@ -0,0 +1,59 @@ +// +// UIButton+Extension.swift +// 35-seminar +// +// Created by 김유림 on 10/24/24. +// + +import UIKit + +enum ConfigurationType { + case plain + case filled +} + +extension UIButton { + func configureButton(configType: ConfigurationType = .plain, + title: String? = nil, + fontSize: CGFloat = 15, + fontWeight: UIFont.Weight = .regular, + systemName: String = "", + pointSize: CGFloat? = nil, + symbolWeight: UIImage.SymbolWeight = .unspecified, + cornerStyle: UIButton.Configuration.CornerStyle? = nil, + foregroundColor: UIColor = .tintColor, + backgroundColor: UIColor = .clear, + removeContentInsets: Bool = false, + for state: UIControl.State = .normal) { + + var config = { + switch configType { + case .plain: + return UIButton.Configuration.plain() + case .filled: + return UIButton.Configuration.filled() + } + }() + + if let title = title { + let attributes: [NSAttributedString.Key: Any] = [.font : UIFont.systemFont(ofSize: fontSize, weight: fontWeight)] + let attributedTitle = NSAttributedString(string: title, attributes: attributes) + self.setAttributedTitle(attributedTitle, for: state) + } + + config.image = UIImage.configureImage(systemName: systemName, pointSize: pointSize, symbolWeight: symbolWeight) + + if let cornerStyle = cornerStyle { + config.cornerStyle = cornerStyle + } + + config.baseForegroundColor = foregroundColor + config.baseBackgroundColor = backgroundColor + + if removeContentInsets { + config.contentInsets = .zero + } + + self.configuration = config + } +} diff --git a/35-seminar/Presentation/Week2/Extensions/UIImage+Extension.swift b/35-seminar/Presentation/Week2/Extensions/UIImage+Extension.swift new file mode 100644 index 0000000..773fac1 --- /dev/null +++ b/35-seminar/Presentation/Week2/Extensions/UIImage+Extension.swift @@ -0,0 +1,20 @@ +// +// UIImageView+Extension.swift +// 35-seminar +// +// Created by 김유림 on 10/25/24. +// + +import UIKit + +extension UIImage { + class func configureImage(systemName: String, pointSize: CGFloat? = nil, symbolWeight: UIImage.SymbolWeight) -> UIImage? { + if let pointSize = pointSize { + let symbolConfig = UIImage.SymbolConfiguration(pointSize: pointSize, weight: symbolWeight) + return UIImage(systemName: systemName, withConfiguration: symbolConfig) + } + + let symbolConfig = UIImage.SymbolConfiguration(weight: symbolWeight) + return UIImage(systemName: systemName, withConfiguration: symbolConfig) + } +} diff --git a/35-seminar/Presentation/Week2/Extensions/UILabel+Extension.swift b/35-seminar/Presentation/Week2/Extensions/UILabel+Extension.swift new file mode 100644 index 0000000..a8928c0 --- /dev/null +++ b/35-seminar/Presentation/Week2/Extensions/UILabel+Extension.swift @@ -0,0 +1,25 @@ +// +// UILabel+Extension.swift +// 35-seminar +// +// Created by 김유림 on 10/15/24. +// + +import UIKit + +extension UILabel { + func configureLabel(alignment: NSTextAlignment = .left, color: UIColor = .label, size: CGFloat, weight: UIFont.Weight, text: String? = nil, numberOfLines: Int = 1) { + self.textAlignment = alignment + self.textColor = color + self.font = .systemFont(ofSize: size, weight: weight) + self.text = text + self.numberOfLines = numberOfLines + } + + func setLineSpacing(_ lineSpacing: CGFloat) { + guard let text = self.text else { return } + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = lineSpacing + self.attributedText = NSAttributedString(string: text, attributes: [.paragraphStyle: paragraphStyle]) + } +} diff --git a/35-seminar/Presentation/Week2/Models/Feedback.swift b/35-seminar/Presentation/Week2/Models/Feedback.swift new file mode 100644 index 0000000..f6cd46f --- /dev/null +++ b/35-seminar/Presentation/Week2/Models/Feedback.swift @@ -0,0 +1,18 @@ +// +// Feedback.swift +// 35-seminar +// +// Created by 김유림 on 10/26/24. +// + +import Foundation + +struct Feedback { + var title: String? + var author: String? + var starCount: Int? + var authorDate: Date? + var content: String? + var developerContent: String? + var developerDate: Date? +} diff --git a/35-seminar/Presentation/Week2/Models/StarColor.swift b/35-seminar/Presentation/Week2/Models/StarColor.swift new file mode 100644 index 0000000..eb4ba2e --- /dev/null +++ b/35-seminar/Presentation/Week2/Models/StarColor.swift @@ -0,0 +1,14 @@ +// +// StarColor.swift +// 35-seminar +// +// Created by 김유림 on 10/26/24. +// + +import Foundation + +enum StarColor { + case tint + case gray + case orange +} diff --git a/35-seminar/Presentation/Week2/Practice/PractScrollViewController.swift b/35-seminar/Presentation/Week2/Practice/PractScrollViewController.swift new file mode 100644 index 0000000..b486730 --- /dev/null +++ b/35-seminar/Presentation/Week2/Practice/PractScrollViewController.swift @@ -0,0 +1,74 @@ +// +// PractScrollViewController.swift +// 35-seminar +// +// Created by 김유림 on 10/15/24. +// + +import UIKit +import SnapKit + +class PractScrollViewController: UIViewController { + + // MARK: - Properties + private let scrollView = UIScrollView() + private var contentView = UIView() + private var redView = UIView() + private let yellowView = UIView() + private let greenView = UIView() + + // MARK: - Methods + override func viewDidLoad() { + super.viewDidLoad() + setUI() + setHierarchy() + setConstraints() + } + + func setUI() { + view.backgroundColor = .lightGray + redView.backgroundColor = .red + yellowView.backgroundColor = .yellow + greenView.backgroundColor = .green + } + + func setHierarchy() { + view.addSubview(scrollView) + scrollView.addSubview(contentView) + + [redView, yellowView, greenView].forEach { + contentView.addSubview($0) + } + } + + func setConstraints() { + scrollView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + contentView.snp.makeConstraints { + $0.edges.equalToSuperview() + $0.width.equalToSuperview() + $0.height.greaterThanOrEqualToSuperview().priority(.low) + } + + redView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(200) + } + + yellowView.snp.makeConstraints { + $0.top.equalTo(redView.snp.bottom) + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(200) + } + + greenView.snp.makeConstraints { + $0.top.equalTo(yellowView.snp.bottom) + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(200) +// $0.bottom.equalToSuperview() + } + } +} diff --git a/35-seminar/Presentation/Week2/Reusables/BorderView.swift b/35-seminar/Presentation/Week2/Reusables/BorderView.swift new file mode 100644 index 0000000..02eabed --- /dev/null +++ b/35-seminar/Presentation/Week2/Reusables/BorderView.swift @@ -0,0 +1,21 @@ +// +// BorderView.swift +// 35-seminar +// +// Created by 김유림 on 10/22/24. +// + +import UIKit +import SnapKit + +class BorderView: UIView { + + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .systemGray4 + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/35-seminar/Presentation/Week2/Reusables/ContentLabel.swift b/35-seminar/Presentation/Week2/Reusables/ContentLabel.swift new file mode 100644 index 0000000..b88c8ee --- /dev/null +++ b/35-seminar/Presentation/Week2/Reusables/ContentLabel.swift @@ -0,0 +1,20 @@ +// +// ContentLabel.swift +// 35-seminar +// +// Created by 김유림 on 10/15/24. +// + +import UIKit + +class ContentLabel: UILabel { + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLabel(color: .label, size: 15, weight: .regular, numberOfLines: 0) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/35-seminar/Presentation/Week2/Reusables/StarStackView.swift b/35-seminar/Presentation/Week2/Reusables/StarStackView.swift new file mode 100644 index 0000000..489d23d --- /dev/null +++ b/35-seminar/Presentation/Week2/Reusables/StarStackView.swift @@ -0,0 +1,116 @@ +// +// StarStackView.swift +// 35-seminar +// +// Created by 김유림 on 10/22/24. +// + +import UIKit + +protocol StarStackViewDelegate: AnyObject { + func starStackView(_ view: StarStackView, newCount: Int) +} + +class StarStackView: UIStackView { + + // MARK: - Properties + weak var delegate: StarStackViewDelegate? + + private var starCount: Int = 0 + private var starColor: StarColor = .tint + + private let starImageView1 = UIImageView() + private let starImageView2 = UIImageView() + private let starImageView3 = UIImageView() + private let starImageView4 = UIImageView() + private let starImageView5 = UIImageView() + + private let starEmptyImage = UIImage.configureImage(systemName: "star", symbolWeight: .regular) + private let starFilledImage = UIImage.configureImage(systemName: "star.fill", symbolWeight: .regular) + + // MARK: - Methods + override init(frame: CGRect) { + super.init(frame: frame) + setUI() + setHierarchy() + updateStarImage() + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUI() { + self.axis = .horizontal + self.distribution = .fillEqually + + [starImageView1 + ,starImageView2 + ,starImageView3 + ,starImageView4 + ,starImageView5].forEach { $0.contentMode = .scaleAspectFit } + } + + private func setHierarchy() { + [starImageView1 + ,starImageView2 + ,starImageView3 + ,starImageView4 + ,starImageView5].forEach { self.addArrangedSubview($0) } + } + + private func updateStarImage() { + for (index, view) in self.arrangedSubviews.enumerated() { + guard let imageView = view as? UIImageView else { continue } + + if index < starCount { + imageView.image = starFilledImage + } else { + imageView.image = starEmptyImage + } + + switch starColor { + case .tint: + imageView.tintColor = .tintColor + case .gray: + imageView.tintColor = .secondaryLabel + case .orange: + imageView.tintColor = .systemOrange + } + } + } + + func bind(_ starCount: Int, _ color: StarColor) { + self.starCount = starCount + self.starColor = color + updateStarImage() + } + + // 드래그 제스처 핸들러 + @objc func handlePangesture(_ gesture: UIPanGestureRecognizer) { + let location = gesture.location(in: self) + let startWidth = bounds.width / 5 + let selectedStarIndex = Int(location.x / startWidth) + let newCount = selectedStarIndex + 1 + + if newCount != starCount { + starCount = newCount + updateStarImage() + delegate?.starStackView(self, newCount: newCount) + } + } + + // 클릭 제스처 핸들러 + @objc func handleTapGesture(_ gesture: UITapGestureRecognizer) { + let location = gesture.location(in: self) + let startWidth = bounds.width / 5 + let selectedStarIndex = Int(location.x / startWidth) + let newCount = selectedStarIndex + 1 + + if newCount != starCount { + starCount = newCount + updateStarImage() + delegate?.starStackView(self, newCount: newCount) + } + } +} diff --git a/35-seminar/Presentation/Week2/Reusables/SubtitleLabel.swift b/35-seminar/Presentation/Week2/Reusables/SubtitleLabel.swift new file mode 100644 index 0000000..48b7e50 --- /dev/null +++ b/35-seminar/Presentation/Week2/Reusables/SubtitleLabel.swift @@ -0,0 +1,20 @@ +// +// SubtitleLabel.swift +// 35-seminar +// +// Created by 김유림 on 10/15/24. +// + +import UIKit + +class SubtitleLabel: UILabel { + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLabel(color: .secondaryLabel, size: 15, weight: .regular) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/35-seminar/Presentation/Week2/Reusables/TitleLabel.swift b/35-seminar/Presentation/Week2/Reusables/TitleLabel.swift new file mode 100644 index 0000000..b7e2e11 --- /dev/null +++ b/35-seminar/Presentation/Week2/Reusables/TitleLabel.swift @@ -0,0 +1,20 @@ +// +// TitleLabel.swift +// 35-seminar +// +// Created by 김유림 on 10/15/24. +// + +import UIKit + +class TitleLabel: UILabel { + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLabel(size: 23, weight: .bold) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/35-seminar/Presentation/Week2/View/AllFeedbackViewController.swift b/35-seminar/Presentation/Week2/View/AllFeedbackViewController.swift new file mode 100644 index 0000000..4e79466 --- /dev/null +++ b/35-seminar/Presentation/Week2/View/AllFeedbackViewController.swift @@ -0,0 +1,16 @@ +// +// AllFeedbackViewController.swift +// 35-seminar +// +// Created by 김유림 on 10/25/24. +// + +import UIKit + +class AllFeedbackViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + } +} diff --git a/35-seminar/Presentation/Week2/View/AppDetailView.swift b/35-seminar/Presentation/Week2/View/AppDetailView.swift new file mode 100644 index 0000000..5fa2344 --- /dev/null +++ b/35-seminar/Presentation/Week2/View/AppDetailView.swift @@ -0,0 +1,783 @@ +// +// AppDetailView 2.swift +// 35-seminar +// +// Created by 김유림 on 10/25/24. +// + + +import UIKit +import SnapKit + +protocol FeedbackDelegate: AnyObject { + func dataBind(feedback: Feedback) +} + +class AppDetailView: UIView { + + // MARK: - Properties + private var feedback: Feedback? + private let initialFeedback = Feedback(title: "김유림", author: "ISTJ", starCount: 5, authorDate: Date.form(year: 2023, month: 12, day: 20), content: "동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라 만세 무궁화 삼천리 화려강산 대한사람 대한으로 길이 보전하세", developerContent: "안녕하세요, 토스팀입니다. 소중한 의견을 주셔서 너무나 감사합니다.", developerDate: Date.form(year: 2024, month: 5, day: 30)) + + let scrollView = UIScrollView() + private let contentStackView = UIStackView() + + // 타이틀뷰 + private let titleView = UIView() + private let iconImageView = UIImageView() + private let titleLabel = TitleLabel() + private let subtitleLabel = SubtitleLabel() + let openButton = UIButton() + let shareButton = UIButton() + + // 요약뷰 + private let summaryStackView = UIStackView() + private let verticalBorderView1 = BorderView() + private let verticalBorderView2 = BorderView() + + // 1번칸 + private let summaryRatingStackView = UIStackView() + private let summaryRatingTitleLabel = SubtitleLabel() + private let summaryRatingAverageLabel = SubtitleLabel() + private let summaryRatingStarStackView = StarStackView() + // 2번칸 + private let summaryPrizeStackView = UIStackView() + private let summaryPrizeTitleLabel = SubtitleLabel() + private let summaryPrizeContentImageView = UIImageView() + private let summaryPrizeSubtitleLabel = SubtitleLabel() + //3번칸 + private let summaryAgeStackView = UIStackView() + private let summaryAgeTitleLabel = SubtitleLabel() + private let summaryAgeLimitLabel = SubtitleLabel() + private let summaryAgeSubtitleLabel = SubtitleLabel() + + // 버전 뷰 + private let versionView = UIView() + private let versionTitleLabel = TitleLabel() + private let versionSubtitleLabel = SubtitleLabel() + private let versionContentLabel = ContentLabel() + let versionRecordButton = UIButton() + private let versionDateLabel = SubtitleLabel() + + // 미리보기뷰 + private let previewView = UIView() + private let previewTitleLabel = TitleLabel() + private let previewImageView = UIImageView() + private let previewDeviceImageView = UIImageView() + private let previewDeviceLabel = SubtitleLabel() + + // 앱 설명 뷰 + private let descriptionView = UIView() + private let descriptionLabel = ContentLabel() + let descriptionMoreButton = UIButton() + + private let developerView = UIView() + private let developerNameLabel = ContentLabel() + private let developerRoleLabel = SubtitleLabel() + private let chevronImageView = UIImageView() + let developerButton = UIButton() + + // 피드백 요약 뷰 + private let feedbackSummaryView = UIView() + private let feedbackSummaryTitleLabel = TitleLabel() + private let feedbackSummaryAverageLabel = TitleLabel() + private let feedbackSummarySubtitleLabel = SubtitleLabel() + let feedbackSummaryAllButton = UIButton() + private let feedbackSummaryStarsImageView = UIImageView() + private let feedbackSummaryCountLabel = SubtitleLabel() + + // 피드백 뷰 + private let feedbackView = UIView() + private let feedbackTapToRateStackView = UIStackView() + private let tapToRateLabel = SubtitleLabel() + let tapToRateStarStackView = StarStackView() + + private let feedbackBoxStackView = UIStackView() + private let feedbackTitleStackView = UIStackView() + private let feedbackTitleLabel = ContentLabel() + private let feedbackDateLabel = SubtitleLabel() + + private let feedbackSubtitleStackView = UIStackView() + private let feedbackStarStackView = StarStackView() + private let feedbackAuthorLabel = SubtitleLabel() + + private let feedbackContentView = UIView() + private let feedbackContentLabel = ContentLabel() + private let feedbackDeveloperTitleLabel = ContentLabel() + private let feedbackDeveloperContentView = UIView() + private let feedbackDeveloperContentLabel = ContentLabel() + private let feedbackDeveloperDateLabel = SubtitleLabel() + let feedbackMoreButton1 = UIButton() + let feedbackMoreButton2 = UIButton() + + let feedbackWriteButton = UIButton() + let appSupportButton = UIButton() + + // MARK: - Methods + override init(frame: CGRect) { + super.init(frame: frame) + feedback = initialFeedback + setUI() + setHierarchy() + setConstraints() + tapToRateStarStackView.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: UI + private func setUI() { + self.backgroundColor = .systemBackground + contentStackView.axis = .vertical + contentStackView.spacing = 10 + + setTitleViewUI() + setSummaryViewUI() + setVersionViewUI() + setPreviewViewUI() + setDescriptionViewUI() + setFeedbackSummaryViewUI() + setFeedbackViewUI() + } + + private func setTitleViewUI() { + iconImageView.image = UIImage(named: "toss_icon") + iconImageView.clipsToBounds = true + iconImageView.layer.cornerRadius = 20 + iconImageView.layer.borderColor = UIColor.systemGray5.cgColor + iconImageView.layer.borderWidth = 1 + + titleLabel.configureLabel(size: 23, weight: .semibold, text: "토스") + + subtitleLabel.text = "금융이 쉬워진다" + + openButton.configureButton(configType: .filled, + title: "열기", + fontSize: 16, + fontWeight: .bold, + cornerStyle: .capsule, + foregroundColor: .white, + backgroundColor: .tintColor) + + shareButton.configureButton(systemName: "square.and.arrow.up", + pointSize: 16, + symbolWeight: .semibold) + + } + + private func setSummaryViewUI() { + summaryStackView.axis = .horizontal + summaryStackView.alignment = .center + + [summaryRatingStackView, summaryPrizeStackView, summaryAgeStackView].forEach { + $0.axis = .vertical + $0.alignment = .center + $0.distribution = .equalSpacing + } + + [summaryRatingTitleLabel, summaryPrizeTitleLabel, summaryAgeTitleLabel].forEach { + $0.configureLabel(color: .secondaryLabel, size: 11, weight: .regular) + $0.textAlignment = .center + } + + [summaryRatingAverageLabel, summaryAgeLimitLabel].forEach { + $0.configureLabel(color: .secondaryLabel, size: 22, weight: .bold) + $0.textAlignment = .center + } + + [summaryPrizeSubtitleLabel, summaryAgeSubtitleLabel].forEach { + $0.configureLabel(color: .secondaryLabel, size: 13, weight: .regular) + $0.textAlignment = .center + } + + summaryRatingTitleLabel.text = "8.4만개의 평가" + summaryRatingAverageLabel.text = "4.4" + summaryRatingStarStackView.bind(4, .gray) + + summaryPrizeTitleLabel.text = "수상" + summaryPrizeContentImageView.image = UIImage(systemName: "person") + summaryPrizeContentImageView.tintColor = .secondaryLabel + summaryPrizeContentImageView.contentMode = .scaleAspectFit + summaryPrizeSubtitleLabel.text = "앱" + + summaryAgeTitleLabel.text = "연령" + summaryAgeLimitLabel.text = "4+" + summaryAgeSubtitleLabel.text = "세" + } + + private func setVersionViewUI() { + versionTitleLabel.text = "새로운 소식" + versionSubtitleLabel.text = "버전 5.185.0" + versionContentLabel.text = "• 구석구석 숨어있던 버그들을 잡았어요. 또 다른 버그가 나타나면 토스 고객센터를 찾아주세요. 늘 열려있답니다. 365일 24시간 언제든지요." + versionContentLabel.setLineSpacing(8) + + versionRecordButton.configureButton(title: "버전 기록", fontSize: 17, fontWeight: .regular, removeContentInsets: true) + versionDateLabel.text = "3시간 전" + } + + private func setPreviewViewUI() { + previewTitleLabel.text = "미리 보기" + + previewImageView.image = UIImage(named: "toss_preview") // NSBundle 오류 발생 + previewImageView.contentMode = .scaleAspectFill + previewImageView.clipsToBounds = true + previewImageView.layer.cornerRadius = 20 + previewImageView.layer.borderColor = UIColor.systemGray5.cgColor + previewImageView.layer.borderWidth = 1 + + previewDeviceImageView.image = UIImage.configureImage(systemName: "iphone", symbolWeight: .regular)?.withTintColor(.secondaryLabel) + previewDeviceImageView.contentMode = .scaleAspectFit + + previewDeviceLabel.configureLabel(color: .secondaryLabel, size: 13, weight: .semibold, text: "iPhone") + } + + private func setDescriptionViewUI() { + let description = """ +토스뱅크, 토스증권 서비스를 이용하시려면 토스 앱 설치가 필요합니다. + • 내 금융 현황을 한눈에, 홈•소비 + • 모든 계좌의 모든 정보를 한 곳에서, 따로 보았던 예적금, 청약, 증권, 대출 계좌의 정보를 한 곳에서 확인할 수 있어 요. + • 얼마나 벌고 얼마나 썼을까? 한 달 동안의 수입과 소비를 시간순으로 모아볼 수 있고, 소비 분석 리포트도 제공해드 려요. + • 카드 실적 헷갈릴 필요 없이, 실적을 충족한 카드가 무엇 인지 얼마나 더 써야 실적을 달성하는지 한눈에 확인할 수 있어요. + • 매달 고정적으로 나가는 보험비, 생활요금, 구독료 등도 쉽게 확인할 수 있어요. + • 평생 무료로 간편하고 안전하게, 송금 + • 송금을 자유롭게, 토스에서는 은행 상관없이 수수료가 평 생 무료에요. + • 송금을 안전하게, 송금 전 사기계좌를 미리 조회해 안전 하게 송금할 수 있어요. + • 송금을 간편하게, 단 한 번의 터치까지 줄였어요. 최소한 의 터치로 송금하세요. + • 그리고 마음까지, 간단한 메시지와 이모티콘을 함께 보내 보세요. +""" + descriptionLabel.text = description + descriptionLabel.numberOfLines = 3 + descriptionLabel.setLineSpacing(8) + + descriptionMoreButton.configureButton(title: "더 보기", fontWeight: .light, removeContentInsets: true) + + developerNameLabel.configureLabel(color: .tintColor, size: 15, weight: .regular, text: "Viva Republica") + + developerRoleLabel.configureLabel(color: .secondaryLabel, size: 14, weight: .regular, text: "개발자") + + chevronImageView.image = UIImage(systemName: "chevron.right") + chevronImageView.contentMode = .scaleAspectFit + chevronImageView.tintColor = .secondaryLabel + } + + private func setFeedbackSummaryViewUI() { + feedbackSummaryTitleLabel.text = "평가 및 리뷰" + + feedbackSummaryAverageLabel.configureLabel(color: .label, size: 60, weight: .bold, text: "4.4") + + feedbackSummarySubtitleLabel.configureLabel(color: .secondaryLabel, size: 15, weight: .bold, text: "5점 만점") + + feedbackSummaryAllButton.configureButton(title: "모두 보기", removeContentInsets: true) + + feedbackSummaryStarsImageView.image = UIImage(named: "summary_stars") + feedbackSummaryStarsImageView.contentMode = .scaleAspectFit + + feedbackSummaryCountLabel.text = "8.4만개의 평가" + } + + private func setFeedbackViewUI() { + feedbackTapToRateStackView.axis = .horizontal + feedbackTapToRateStackView.spacing = 10 + + tapToRateLabel.configureLabel(color: .secondaryLabel, size: 17, weight: .medium, text: "탭하여 평가하기:") + tapToRateLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + tapToRateStarStackView.setContentHuggingPriority(.defaultLow, for: .horizontal) + tapToRateStarStackView.bind(feedback?.starCount ?? 0, .tint) + + feedbackBoxStackView.axis = .vertical + feedbackBoxStackView.backgroundColor = .systemGray6 + feedbackBoxStackView.layer.cornerRadius = 10 + feedbackBoxStackView.spacing = 3 + feedbackBoxStackView.isLayoutMarginsRelativeArrangement = true + feedbackBoxStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 18, leading: 20, bottom: 18, trailing: 20) + + + [feedbackTitleStackView, feedbackSubtitleStackView].forEach { + $0.axis = .horizontal + $0.spacing = 10 + } + + feedbackTitleLabel.configureLabel(size: 15, weight: .bold, text: feedback?.title) + + feedbackDateLabel.configureLabel(alignment: .right, color: .secondaryLabel, size: 15, weight: .regular, text: Date.formattedDate(date: feedback?.authorDate)) + feedbackDateLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + feedbackStarStackView.bind(feedback?.starCount ?? 0, .orange) + feedbackStarStackView.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + feedbackAuthorLabel.configureLabel(alignment: .right, color: .secondaryLabel, size: 14, weight: .regular, text: feedback?.author) + + feedbackContentLabel.text = feedback?.content + feedbackContentLabel.setLineSpacing(4) + + feedbackDeveloperTitleLabel.configureLabel(size: 15, weight: .semibold, text: "개발자 답변") + + feedbackDeveloperContentLabel.text = feedback?.developerContent + feedbackDeveloperContentLabel.setLineSpacing(4) + + feedbackDeveloperDateLabel.configureLabel(color: .secondaryLabel, size: 15, weight: .regular, text: Date.formattedDate(date: feedback?.developerDate)) + + [feedbackMoreButton1, feedbackMoreButton2].forEach { + $0.configureButton(title: "더 보기", removeContentInsets: true) + } + + feedbackWriteButton.configureButton(title: " 리뷰 작성", fontSize: 17, systemName: "square.and.pencil") + + appSupportButton.configureButton(title: " 앱 지원", fontSize: 17, systemName: "questionmark.circle") + } + + // MARK: - Hierarchy + private func setHierarchy() { + setBaseHierarchy() + setTitleViewHierarchy() + setSummaryViewHierarchy() + setVersioinViewHierarchy() + setPreviewViewHierarchy() + setDescriptionViewHierarchy() + setFeedbackSummaryViewHierarchy() + setFeedbackViewHierarchy() + } + + private func setBaseHierarchy() { + self.addSubview(scrollView) + scrollView.addSubview(contentStackView) + + [titleView, summaryStackView, versionView, previewView, descriptionView, feedbackSummaryView, feedbackView].forEach { + let borderView = BorderView() + contentStackView.addArrangedSubview($0) + contentStackView.addArrangedSubview(borderView) + borderView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(20) + $0.height.equalTo(0.4) + } + } + } + + private func setTitleViewHierarchy() { + [iconImageView, titleLabel, subtitleLabel, openButton, shareButton].forEach { + titleView.addSubview($0) + } + } + + private func setSummaryViewHierarchy() { + [summaryRatingStackView, verticalBorderView1, summaryPrizeStackView, verticalBorderView2, summaryAgeStackView].forEach { + summaryStackView.addArrangedSubview($0) + } + + [summaryRatingTitleLabel, summaryRatingAverageLabel, summaryRatingStarStackView].forEach { + summaryRatingStackView.addArrangedSubview($0) + } + + [summaryPrizeTitleLabel, summaryPrizeContentImageView, summaryPrizeSubtitleLabel].forEach { + summaryPrizeStackView.addArrangedSubview($0) + } + + [summaryAgeTitleLabel, summaryAgeLimitLabel, summaryAgeSubtitleLabel].forEach { + summaryAgeStackView.addArrangedSubview($0) + } + } + + private func setVersioinViewHierarchy() { + [versionTitleLabel, versionSubtitleLabel, versionContentLabel, versionRecordButton, versionDateLabel].forEach { + versionView.addSubview($0) + } + } + + private func setPreviewViewHierarchy() { + [previewTitleLabel, previewImageView, previewDeviceImageView, previewDeviceLabel].forEach { + previewView.addSubview($0) + } + } + + private func setDescriptionViewHierarchy() { + [descriptionLabel, descriptionMoreButton, developerView].forEach { + descriptionView.addSubview($0) + } + + [developerNameLabel, developerRoleLabel, chevronImageView, developerButton].forEach { + developerView.addSubview($0) + } + } + + private func setFeedbackSummaryViewHierarchy() { + [feedbackSummaryTitleLabel, feedbackSummaryAverageLabel, feedbackSummarySubtitleLabel, feedbackSummaryAllButton, feedbackSummaryStarsImageView, feedbackSummaryCountLabel].forEach { + feedbackSummaryView.addSubview($0) + } + } + + private func setFeedbackViewHierarchy() { + [feedbackTapToRateStackView, feedbackBoxStackView, feedbackWriteButton, appSupportButton].forEach { + feedbackView.addSubview($0) + } + + [tapToRateLabel, tapToRateStarStackView].forEach { + feedbackTapToRateStackView.addArrangedSubview($0) + } + + [feedbackTitleStackView, feedbackSubtitleStackView, feedbackContentView, feedbackDeveloperContentView].forEach { + feedbackBoxStackView.addArrangedSubview($0) + } + + [feedbackTitleLabel, feedbackDateLabel].forEach { + feedbackTitleStackView.addArrangedSubview($0) + } + + [feedbackStarStackView, feedbackAuthorLabel].forEach { + feedbackSubtitleStackView.addArrangedSubview($0) + } + + [feedbackContentLabel, feedbackMoreButton1].forEach { + feedbackContentView.addSubview($0) + } + + [feedbackDeveloperTitleLabel, feedbackDeveloperDateLabel, feedbackDeveloperContentLabel, feedbackMoreButton2].forEach { + feedbackDeveloperContentView.addSubview($0) + } + } + + // MARK: - Constraints + private func setConstraints() { + setBaseConstraints() + setTitleViewConstraints() + setSummaryViewConstraints() + setVersionViewConstraints() + setPreviewViewConstraints() + setDescriptionViewConstraints() + setFeedbackSummaryViewConstraints() + setFeedbackViewConstraints() + } + + private func setBaseConstraints() { + scrollView.snp.makeConstraints { + $0.edges.equalTo(self.safeAreaLayoutGuide) + } + + contentStackView.snp.makeConstraints { + $0.edges.equalToSuperview() + $0.width.equalToSuperview() + $0.height.greaterThanOrEqualToSuperview().priority(.low) + } + } + + private func setTitleViewConstraints() { + titleView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + } + + iconImageView.snp.makeConstraints { + $0.size.equalTo(128) + $0.leading.equalToSuperview().inset(20) + $0.top.equalToSuperview() + $0.bottom.equalToSuperview().inset(10) + } + + titleLabel.snp.makeConstraints { + $0.leading.equalTo(iconImageView.snp.trailing).offset(16) + $0.top.equalTo(iconImageView) + } + + subtitleLabel.snp.makeConstraints { + $0.leading.equalTo(titleLabel) + $0.top.equalTo(titleLabel.snp.bottom).offset(5) + } + + openButton.snp.makeConstraints { + $0.leading.equalTo(titleLabel) + $0.bottom.equalTo(iconImageView) + $0.width.equalTo(72) + $0.height.equalTo(32) + } + + shareButton.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(20) + $0.bottom.equalTo(iconImageView) + $0.size.equalTo(32) + } + } + + private func setSummaryViewConstraints() { + summaryStackView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(20) + $0.height.equalTo(72) + } + + [verticalBorderView1, verticalBorderView2].forEach { + $0.snp.makeConstraints { + $0.height.equalTo(40) + $0.width.equalTo(0.4) + } + } + + summaryRatingStackView.snp.makeConstraints { + $0.height.equalToSuperview() + $0.width.equalTo(summaryPrizeStackView) + $0.width.equalTo(summaryAgeStackView) + } + + summaryPrizeStackView.snp.makeConstraints { + $0.height.equalToSuperview() + $0.width.equalTo(summaryRatingStackView) + $0.width.equalTo(summaryAgeStackView) + } + + summaryAgeStackView.snp.makeConstraints { + $0.height.equalToSuperview() + $0.width.equalTo(summaryPrizeStackView) + $0.width.equalTo(summaryRatingStackView) + } + + [summaryRatingTitleLabel, summaryPrizeTitleLabel, summaryAgeTitleLabel].forEach { + $0.snp.makeConstraints { + $0.height.equalTo(20) + } + } + + [summaryRatingAverageLabel, summaryPrizeContentImageView, summaryAgeLimitLabel].forEach { + $0.snp.makeConstraints { + $0.height.equalTo(24) + $0.horizontalEdges.equalToSuperview() + } + } + + [summaryRatingStarStackView, summaryPrizeSubtitleLabel, summaryAgeSubtitleLabel].forEach { + $0.snp.makeConstraints { + $0.height.equalTo(20) + $0.horizontalEdges.equalToSuperview().inset(20) + } + } + } + + private func setVersionViewConstraints() { + versionView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(20) + } + + versionTitleLabel.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.equalToSuperview() + } + + versionSubtitleLabel.snp.makeConstraints { + $0.top.equalTo(versionTitleLabel.snp.bottom).offset(5) + $0.leading.equalTo(versionTitleLabel) + } + + versionContentLabel.snp.makeConstraints { + $0.top.equalTo(versionSubtitleLabel.snp.bottom).offset(16) + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalToSuperview().inset(10) + } + + versionRecordButton.snp.makeConstraints { + $0.bottom.equalTo(versionTitleLabel) + $0.trailing.equalToSuperview() + } + + versionDateLabel.snp.makeConstraints { + $0.bottom.equalTo(versionSubtitleLabel) + $0.trailing.equalToSuperview() + } + } + + private func setPreviewViewConstraints() { + previewView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(20) + } + + previewTitleLabel.snp.makeConstraints { + $0.top.leading.equalToSuperview() + } + + previewImageView.snp.makeConstraints { + $0.top.equalTo(previewTitleLabel.snp.bottom).offset(5) + $0.centerX.equalToSuperview() + $0.height.equalTo(500) + $0.width.equalTo(240) + } + + previewDeviceImageView.snp.makeConstraints { + $0.top.equalTo(previewImageView.snp.bottom).offset(10) + $0.leading.equalToSuperview() + $0.size.equalTo(18) + $0.bottom.equalToSuperview().offset(-10) + } + + previewDeviceLabel.snp.makeConstraints { + $0.bottom.equalTo(previewDeviceImageView) + $0.leading.equalTo(previewDeviceImageView.snp.trailing).offset(5) + } + } + + private func setDescriptionViewConstraints() { + descriptionView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(20) + } + + descriptionLabel.snp.makeConstraints { + $0.top.horizontalEdges.equalToSuperview().offset(10) + } + + descriptionMoreButton.snp.makeConstraints { + $0.bottom.equalTo(descriptionLabel) + $0.trailing.equalToSuperview() + } + + developerView.snp.makeConstraints { + $0.top.equalTo(descriptionLabel.snp.bottom).offset(30) + $0.bottom.equalToSuperview().offset(-30) + $0.horizontalEdges.equalToSuperview() + } + + developerNameLabel.snp.makeConstraints { + $0.top.leading.equalToSuperview() + } + + developerRoleLabel.snp.makeConstraints { + $0.top.equalTo(developerNameLabel.snp.bottom).offset(2) + $0.leading.equalToSuperview() + $0.bottom.equalToSuperview() + } + + chevronImageView.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview() + $0.size.equalTo(20) + } + } + + private func setFeedbackSummaryViewConstraints() { + feedbackSummaryView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(20) + } + + feedbackSummaryTitleLabel.snp.makeConstraints { + $0.leading.top.equalToSuperview() + } + + feedbackSummaryAverageLabel.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.top.equalTo(feedbackSummaryTitleLabel.snp.bottom) + $0.width.equalTo(100) + $0.height.equalTo(64) + } + + feedbackSummarySubtitleLabel.snp.makeConstraints { + $0.centerX.equalTo(feedbackSummaryAverageLabel) + $0.top.equalTo(feedbackSummaryAverageLabel.snp.bottom) + $0.bottom.equalToSuperview().offset(-10) + } + + feedbackSummaryAllButton.snp.makeConstraints { + $0.trailing.equalToSuperview() + $0.centerY.equalTo(feedbackSummaryTitleLabel) + } + + feedbackSummaryStarsImageView.snp.makeConstraints { + $0.leading.equalTo(feedbackSummaryAverageLabel.snp.trailing).offset(20) + $0.trailing.equalToSuperview() + $0.verticalEdges.equalTo(feedbackSummaryAverageLabel).inset(5) + } + + feedbackSummaryCountLabel.snp.makeConstraints { + $0.trailing.equalToSuperview() + $0.centerY.equalTo(feedbackSummarySubtitleLabel) + } + } + + private func setFeedbackViewConstraints() { + feedbackView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview().inset(20) + } + + feedbackTapToRateStackView.snp.makeConstraints { + $0.top.horizontalEdges.equalToSuperview() + $0.height.equalTo(28) + } + + feedbackBoxStackView.snp.makeConstraints { + $0.top.equalTo(feedbackTapToRateStackView.snp.bottom).offset(20) + $0.horizontalEdges.equalToSuperview() + } + + feedbackStarStackView.snp.makeConstraints { + $0.height.equalTo(16) + $0.width.equalTo(80) + } + + feedbackContentLabel.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + $0.verticalEdges.equalToSuperview().inset(10) + } + + feedbackMoreButton1.snp.makeConstraints { + $0.trailing.equalToSuperview() + $0.bottom.equalTo(feedbackContentLabel) + } + + feedbackDeveloperTitleLabel.snp.makeConstraints { + $0.leading.equalToSuperview() + $0.top.equalToSuperview().offset(5) + } + + feedbackDeveloperDateLabel.snp.makeConstraints { + $0.trailing.equalToSuperview() + $0.centerY.equalTo(feedbackDeveloperTitleLabel) + } + + feedbackDeveloperContentLabel.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + $0.top.equalTo(feedbackDeveloperTitleLabel.snp.bottom).offset(5) + $0.bottom.equalToSuperview() + } + + feedbackMoreButton2.snp.makeConstraints { + $0.trailing.equalToSuperview() + $0.bottom.equalTo(feedbackDeveloperContentLabel) + } + + feedbackWriteButton.snp.makeConstraints { + $0.top.equalTo(feedbackBoxStackView.snp.bottom).offset(20) + $0.leading.equalTo(feedbackBoxStackView) + $0.bottom.equalToSuperview().offset(-10) + } + + appSupportButton.snp.makeConstraints { + $0.top.equalTo(feedbackBoxStackView.snp.bottom).offset(20) + $0.trailing.equalTo(feedbackBoxStackView) + } + } + + func dataBind(feedback: Feedback) { + self.feedback = feedback + feedbackTitleLabel.text = feedback.title + feedbackAuthorLabel.text = feedback.author + feedbackStarStackView.bind(feedback.starCount ?? 0, .orange) + feedbackDateLabel.text = Date.formattedDate(date: feedback.authorDate) + feedbackContentLabel.text = feedback.content + feedbackDeveloperContentLabel.text = feedback.developerContent + feedbackDeveloperDateLabel.text = Date.formattedDate(date: feedback.developerDate) + tapToRateStarStackView.bind(feedback.starCount ?? 0, .tint) + + // 개발자 답변 있는지 확인 + if feedback.developerContent == nil { + feedbackDeveloperContentView.isHidden = true + } else { + feedbackDeveloperContentView.isHidden = false + } + } + + func expandDescriptionLabel() { + descriptionLabel.numberOfLines = 0 + descriptionMoreButton.isHidden = true + } +} + +extension AppDetailView: StarStackViewDelegate { + func starStackView(_ view: StarStackView, newCount: Int) { + feedback?.starCount = newCount + feedbackStarStackView.bind(newCount, .orange) + } +} diff --git a/35-seminar/Presentation/Week2/View/AppDetailViewController.swift b/35-seminar/Presentation/Week2/View/AppDetailViewController.swift new file mode 100644 index 0000000..a3ec4f6 --- /dev/null +++ b/35-seminar/Presentation/Week2/View/AppDetailViewController.swift @@ -0,0 +1,114 @@ +// +// AppDetailViewController.swift +// 35-seminar +// +// Created by 김유림 on 10/15/24. +// + +import UIKit + +class AppDetailViewController: UIViewController { + + // MARK: - Properties + private let appDetailView = AppDetailView() + + // MARK: - Methods + override func loadView() { + view = appDetailView + } + + override func viewDidLoad() { + super.viewDidLoad() + setNavigationBar() + setButtonAction() + setStarStackViewGesture() + appDetailView.scrollView.delegate = self + } + + private func setNavigationBar() { + let iconImageView = UIImageView() + iconImageView.clipsToBounds = true + iconImageView.contentMode = .scaleAspectFit + iconImageView.image = .tossIcon + iconImageView.layer.cornerRadius = 8 + iconImageView.layer.borderColor = UIColor.systemGray5.cgColor + iconImageView.layer.borderWidth = 0.6 + iconImageView.snp.makeConstraints { + $0.size.equalTo(28) + } + + let rightBarButton = UIButton() + rightBarButton.configureButton(configType: .filled, + title: "열기", + fontSize: 16, + fontWeight: .bold, + cornerStyle: .capsule, + foregroundColor: .white, + backgroundColor: .tintColor) + rightBarButton.snp.makeConstraints { + $0.width.equalTo(72) + $0.height.equalTo(32) + } + + self.navigationItem.titleView = iconImageView + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightBarButton) + } + + private func setButtonAction() { + appDetailView.versionRecordButton.addTarget(self, action: #selector (tappedVersionRecordButton), for: .touchUpInside) + + appDetailView.feedbackSummaryAllButton.addTarget(self, action: #selector (tappedFeedbackSummaryAllButton), for: .touchUpInside) + + appDetailView.feedbackWriteButton.addTarget(self, action: #selector(tappedFeedbackWriteButton), for: .touchUpInside) + + appDetailView.descriptionMoreButton.addTarget(self, action: #selector(tappedDescriptionMoreButton), for: .touchUpInside) + } + + private func setStarStackViewGesture() { + let panGesture = UIPanGestureRecognizer(target: appDetailView.tapToRateStarStackView, action: #selector(appDetailView.tapToRateStarStackView.handlePangesture)) + appDetailView.tapToRateStarStackView.addGestureRecognizer(panGesture) + + let tapGesture = UITapGestureRecognizer(target: appDetailView.tapToRateStarStackView, action: #selector(appDetailView.tapToRateStarStackView.handlePangesture)) + appDetailView.tapToRateStarStackView.addGestureRecognizer(tapGesture) + } + + @objc private func tappedVersionRecordButton() { + self.navigationController?.pushViewController(VersionRecordViewController(), animated: true) + } + + @objc private func tappedFeedbackSummaryAllButton() { + self.navigationController?.pushViewController(AllFeedbackViewController(), animated: true) + } + + @objc private func tappedFeedbackWriteButton() { + let feedbackWriteVC = FeedbackWriteViewController() + feedbackWriteVC.delegate = self + self.present(feedbackWriteVC, animated: true) + } + + @objc private func tappedDescriptionMoreButton() { + appDetailView.expandDescriptionLabel() + } +} + + +extension AppDetailViewController: FeedbackDelegate { + func dataBind(feedback: Feedback) { + appDetailView.dataBind(feedback: feedback) + } +} + +extension AppDetailViewController: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let offsetY = scrollView.contentOffset.y + let triggerOffset: CGFloat = 80 + + if offsetY > triggerOffset { + navigationItem.titleView?.isHidden = false + navigationItem.rightBarButtonItem?.isHidden = false + } else { + navigationItem.titleView?.isHidden = true + navigationItem.rightBarButtonItem?.isHidden = true + } + } +} diff --git a/35-seminar/Presentation/Week2/View/FeedbackWriteView.swift b/35-seminar/Presentation/Week2/View/FeedbackWriteView.swift new file mode 100644 index 0000000..32afe08 --- /dev/null +++ b/35-seminar/Presentation/Week2/View/FeedbackWriteView.swift @@ -0,0 +1,158 @@ +// +// FeedbackWriteView.swift +// 35-seminar +// +// Created by 김유림 on 10/25/24. +// + +import UIKit +import SnapKit + +class FeedbackWriteView: UIView { + + // MARK: - Properties + var starCount: Int = 0 + let textViewPlaceHolder = "리뷰(선택사항)" + + private let titleLabel = UILabel() + let cancelButton = UIButton() + let sendButton = UIButton() + + let tapToRateStarStackView = StarStackView() + private let tapToRateGuideLabel = SubtitleLabel() + + private let feedbackStackView = UIStackView() + private let feedbackTitleTextField = UITextField() + let feedbackTextView = UITextView() + + // MARK: - Methods + override init(frame: CGRect) { + super.init(frame: frame) + setUI() + setHierarchy() + setConstraints() + tapToRateStarStackView.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUI() { + self.backgroundColor = .systemBackground + + titleLabel.configureLabel(size: 17, weight: .semibold, text: "리뷰 작성하기") + + cancelButton.configureButton(title: "취소", fontSize: 17, fontWeight: .medium, removeContentInsets: true) + + sendButton.configureButton(title: "보내기", fontSize: 17, fontWeight: .semibold, removeContentInsets: true) + + tapToRateGuideLabel.configureLabel(color: .secondaryLabel, size: 10, weight: .regular, text: "별점을 탭하여 평가하기") + + feedbackStackView.axis = .vertical + feedbackStackView.spacing = 10 + + feedbackTitleTextField.placeholder = "제목" + feedbackTitleTextField.font = .systemFont(ofSize: 17, weight: .regular) + feedbackTitleTextField.setContentHuggingPriority(.defaultHigh, for: .vertical) + + feedbackTextView.textContainerInset = .zero + feedbackTextView.textContainer.lineFragmentPadding = 0 + feedbackTextView.font = .systemFont(ofSize: 17, weight: .regular) + setTextViewPlaceholder() + } + + private func setHierarchy() { + [cancelButton, sendButton, titleLabel, tapToRateStarStackView, tapToRateGuideLabel, feedbackStackView].forEach { + self.addSubview($0) + } + + [feedbackTitleTextField, feedbackTextView].forEach { + let border = BorderView() + feedbackStackView.addArrangedSubview(border) + feedbackStackView.addArrangedSubview($0) + + border.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(0.4) + } + } + } + + private func setConstraints() { + titleLabel.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalToSuperview().offset(20) + } + + cancelButton.snp.makeConstraints { + $0.leading.equalToSuperview().offset(20) + $0.top.equalTo(titleLabel) + } + + sendButton.snp.makeConstraints { + $0.trailing.equalToSuperview().offset(-20) + $0.top.equalTo(titleLabel) + } + + tapToRateStarStackView.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(30) + $0.centerX.equalToSuperview() + $0.width.equalTo(180) + } + + tapToRateGuideLabel.snp.makeConstraints { + $0.top.equalTo(tapToRateStarStackView.snp.bottom).offset(2) + $0.centerX.equalToSuperview() + } + + feedbackStackView.snp.makeConstraints { + $0.top.equalTo(tapToRateGuideLabel.snp.bottom).offset(2) + $0.horizontalEdges.equalToSuperview().inset(20) + $0.bottom.equalTo(self.safeAreaLayoutGuide) + } + } + + func setTextViewPlaceholder() { + feedbackTextView.text = textViewPlaceHolder + feedbackTextView.textColor = .systemGray4 + } + + func setTextViewToWrite() { + if feedbackTextView.text == textViewPlaceHolder { + feedbackTextView.text = nil + feedbackTextView.textColor = .label + } + } + + func setTextViewToEnd() { + let isPlaceHolder = feedbackTextView.text == textViewPlaceHolder + let isEmpty = feedbackTextView.text.isEmpty + + if isPlaceHolder || isEmpty { + setTextViewPlaceholder() + } + } + + func returnFeedback() -> Feedback { + var content = feedbackTextView.text + if content == textViewPlaceHolder { + content = nil + } + + let feedback = Feedback(title: feedbackTitleTextField.text, + author: "김유림", + starCount: starCount, + authorDate: Date(), + content: content, + developerContent: nil, + developerDate: nil) + return feedback + } +} + +extension FeedbackWriteView: StarStackViewDelegate { + func starStackView(_ view: StarStackView, newCount: Int) { + starCount = newCount + } +} diff --git a/35-seminar/Presentation/Week2/View/FeedbackWriteViewController.swift b/35-seminar/Presentation/Week2/View/FeedbackWriteViewController.swift new file mode 100644 index 0000000..7bbcf83 --- /dev/null +++ b/35-seminar/Presentation/Week2/View/FeedbackWriteViewController.swift @@ -0,0 +1,60 @@ +// +// FeedbackWriteViewController.swift +// 35-seminar +// +// Created by 김유림 on 10/25/24. +// + +import UIKit + +class FeedbackWriteViewController: UIViewController { + + // MARK: - Properties + private let feedbackWriteView = FeedbackWriteView() + weak var delegate: FeedbackDelegate? + + // MARK: - Methods + override func loadView() { + view = feedbackWriteView + } + + override func viewDidLoad() { + super.viewDidLoad() + setButtonAction() + setStarStackViewGesture() + feedbackWriteView.feedbackTextView.delegate = self + } + + private func setButtonAction() { + feedbackWriteView.cancelButton.addTarget(self, action: #selector(tappedCancelButton), for: .touchUpInside) + + feedbackWriteView.sendButton.addTarget(self, action: #selector(tappedSendButton), for: .touchUpInside) + } + + private func setStarStackViewGesture() { + let panGesture = UIPanGestureRecognizer(target: feedbackWriteView.tapToRateStarStackView, action: #selector(feedbackWriteView.tapToRateStarStackView.handlePangesture)) + feedbackWriteView.tapToRateStarStackView.addGestureRecognizer(panGesture) + + let tapGesture = UITapGestureRecognizer(target: feedbackWriteView.tapToRateStarStackView, action: #selector(feedbackWriteView.tapToRateStarStackView.handlePangesture)) + feedbackWriteView.tapToRateStarStackView.addGestureRecognizer(tapGesture) + } + + @objc private func tappedCancelButton() { + self.dismiss(animated: true) + } + + @objc private func tappedSendButton() { + delegate?.dataBind(feedback: feedbackWriteView.returnFeedback()) + self.dismiss(animated: true) + } +} + +extension FeedbackWriteViewController: UITextViewDelegate { + func textViewDidBeginEditing(_ textView: UITextView) { + feedbackWriteView.setTextViewToWrite() + } + + func textViewDidEndEditing(_ textView: UITextView) { + feedbackWriteView.setTextViewToEnd() + } +} diff --git a/35-seminar/Presentation/Week2/View/VersionRecordViewController.swift b/35-seminar/Presentation/Week2/View/VersionRecordViewController.swift new file mode 100644 index 0000000..13d5b28 --- /dev/null +++ b/35-seminar/Presentation/Week2/View/VersionRecordViewController.swift @@ -0,0 +1,16 @@ +// +// VersionRecordViewController.swift +// 35-seminar +// +// Created by 김유림 on 10/25/24. +// + +import UIKit + +class VersionRecordViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + } +} diff --git a/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/Contents.json b/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/Contents.json new file mode 100644 index 0000000..2dc0ab4 --- /dev/null +++ b/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "summary_stars_light.png", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "summary_stars_dark.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/summary_stars_dark.png b/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/summary_stars_dark.png new file mode 100644 index 0000000..c3fbfbe Binary files /dev/null and b/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/summary_stars_dark.png differ diff --git a/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/summary_stars_light.png b/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/summary_stars_light.png new file mode 100644 index 0000000..e201c9c Binary files /dev/null and b/35-seminar/Resource/Assets.xcassets/summary_stars.imageset/summary_stars_light.png differ diff --git a/35-seminar/Resource/Assets.xcassets/toss_icon.imageset/Contents.json b/35-seminar/Resource/Assets.xcassets/toss_icon.imageset/Contents.json new file mode 100644 index 0000000..493227c --- /dev/null +++ b/35-seminar/Resource/Assets.xcassets/toss_icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "toss_icon.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/35-seminar/Resource/Assets.xcassets/toss_icon.imageset/toss_icon.png b/35-seminar/Resource/Assets.xcassets/toss_icon.imageset/toss_icon.png new file mode 100644 index 0000000..5e5e38a Binary files /dev/null and b/35-seminar/Resource/Assets.xcassets/toss_icon.imageset/toss_icon.png differ diff --git a/35-seminar/Resource/Assets.xcassets/toss_preview.imageset/Contents.json b/35-seminar/Resource/Assets.xcassets/toss_preview.imageset/Contents.json new file mode 100644 index 0000000..67057af --- /dev/null +++ b/35-seminar/Resource/Assets.xcassets/toss_preview.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "toss_preview.jpeg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/35-seminar/Resource/Assets.xcassets/toss_preview.imageset/toss_preview.jpeg b/35-seminar/Resource/Assets.xcassets/toss_preview.imageset/toss_preview.jpeg new file mode 100644 index 0000000..988b60d Binary files /dev/null and b/35-seminar/Resource/Assets.xcassets/toss_preview.imageset/toss_preview.jpeg differ