diff --git a/Week 5/Shimmy/week5-mission/week5-mission.xcodeproj/project.pbxproj b/Week 5/Shimmy/week5-mission/week5-mission.xcodeproj/project.pbxproj index cfacd61..8a1c624 100644 --- a/Week 5/Shimmy/week5-mission/week5-mission.xcodeproj/project.pbxproj +++ b/Week 5/Shimmy/week5-mission/week5-mission.xcodeproj/project.pbxproj @@ -10,12 +10,13 @@ 03887C312AE7919F008C0635 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03887C302AE7919F008C0635 /* AppDelegate.swift */; }; 03887C332AE7919F008C0635 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03887C322AE7919F008C0635 /* SceneDelegate.swift */; }; 03887C352AE7919F008C0635 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03887C342AE7919F008C0635 /* ViewController.swift */; }; - 03887C382AE7919F008C0635 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 03887C362AE7919F008C0635 /* Main.storyboard */; }; 03887C3A2AE791A0008C0635 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03887C392AE791A0008C0635 /* Assets.xcassets */; }; 03887C3D2AE791A0008C0635 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 03887C3B2AE791A0008C0635 /* LaunchScreen.storyboard */; }; 03887C482AE791A0008C0635 /* week5_missionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03887C472AE791A0008C0635 /* week5_missionTests.swift */; }; 03887C522AE791A0008C0635 /* week5_missionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03887C512AE791A0008C0635 /* week5_missionUITests.swift */; }; 03887C542AE791A0008C0635 /* week5_missionUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03887C532AE791A0008C0635 /* week5_missionUITestsLaunchTests.swift */; }; + 03EABFFB2AEF95AA00C80B72 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EABFFA2AEF95AA00C80B72 /* MainViewController.swift */; }; + 03EABFFD2AF02E2900C80B72 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EABFFC2AF02E2900C80B72 /* MenuViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,7 +41,6 @@ 03887C302AE7919F008C0635 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 03887C322AE7919F008C0635 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 03887C342AE7919F008C0635 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 03887C372AE7919F008C0635 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 03887C392AE791A0008C0635 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 03887C3C2AE791A0008C0635 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 03887C3E2AE791A0008C0635 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -49,6 +49,8 @@ 03887C4D2AE791A0008C0635 /* week5-missionUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "week5-missionUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 03887C512AE791A0008C0635 /* week5_missionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = week5_missionUITests.swift; sourceTree = ""; }; 03887C532AE791A0008C0635 /* week5_missionUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = week5_missionUITestsLaunchTests.swift; sourceTree = ""; }; + 03EABFFA2AEF95AA00C80B72 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + 03EABFFC2AF02E2900C80B72 /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -102,10 +104,11 @@ 03887C302AE7919F008C0635 /* AppDelegate.swift */, 03887C322AE7919F008C0635 /* SceneDelegate.swift */, 03887C342AE7919F008C0635 /* ViewController.swift */, - 03887C362AE7919F008C0635 /* Main.storyboard */, 03887C392AE791A0008C0635 /* Assets.xcassets */, 03887C3B2AE791A0008C0635 /* LaunchScreen.storyboard */, 03887C3E2AE791A0008C0635 /* Info.plist */, + 03FBD2BF2AF0C736002C459B /* Main */, + 03FBD2BB2AF0C6DC002C459B /* Menu */, ); path = "week5-mission"; sourceTree = ""; @@ -127,6 +130,46 @@ path = "week5-missionUITests"; sourceTree = ""; }; + 03FBD2BB2AF0C6DC002C459B /* Menu */ = { + isa = PBXGroup; + children = ( + 03FBD2BE2AF0C6F4002C459B /* ViewController */, + 03FBD2BC2AF0C6E4002C459B /* View */, + ); + path = Menu; + sourceTree = ""; + }; + 03FBD2BC2AF0C6E4002C459B /* View */ = { + isa = PBXGroup; + children = ( + ); + path = View; + sourceTree = ""; + }; + 03FBD2BE2AF0C6F4002C459B /* ViewController */ = { + isa = PBXGroup; + children = ( + 03EABFFC2AF02E2900C80B72 /* MenuViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + 03FBD2BF2AF0C736002C459B /* Main */ = { + isa = PBXGroup; + children = ( + 03FBD2C02AF0C740002C459B /* ViewController */, + ); + path = Main; + sourceTree = ""; + }; + 03FBD2C02AF0C740002C459B /* ViewController */ = { + isa = PBXGroup; + children = ( + 03EABFFA2AEF95AA00C80B72 /* MainViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -233,7 +276,6 @@ files = ( 03887C3D2AE791A0008C0635 /* LaunchScreen.storyboard in Resources */, 03887C3A2AE791A0008C0635 /* Assets.xcassets in Resources */, - 03887C382AE7919F008C0635 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -259,8 +301,10 @@ buildActionMask = 2147483647; files = ( 03887C352AE7919F008C0635 /* ViewController.swift in Sources */, + 03EABFFD2AF02E2900C80B72 /* MenuViewController.swift in Sources */, 03887C312AE7919F008C0635 /* AppDelegate.swift in Sources */, 03887C332AE7919F008C0635 /* SceneDelegate.swift in Sources */, + 03EABFFB2AEF95AA00C80B72 /* MainViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -297,14 +341,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 03887C362AE7919F008C0635 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 03887C372AE7919F008C0635 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; 03887C3B2AE791A0008C0635 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -442,7 +478,6 @@ INFOPLIST_FILE = "week5-mission/Info.plist"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -470,7 +505,6 @@ INFOPLIST_FILE = "week5-mission/Info.plist"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Week 5/Shimmy/week5-mission/week5-mission.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Week 5/Shimmy/week5-mission/week5-mission.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..aab7eaa --- /dev/null +++ b/Week 5/Shimmy/week5-mission/week5-mission.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/Week 5/Shimmy/week5-mission/week5-mission/Assets.xcassets/INHA-UMC-5th.imageset/Contents.json b/Week 5/Shimmy/week5-mission/week5-mission/Assets.xcassets/INHA-UMC-5th.imageset/Contents.json new file mode 100644 index 0000000..9b391c7 --- /dev/null +++ b/Week 5/Shimmy/week5-mission/week5-mission/Assets.xcassets/INHA-UMC-5th.imageset/Contents.json @@ -0,0 +1,33 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + }, + { + "filename" : "INHA-UMC-5th.png", + "idiom" : "iphone", + "scale" : "1x" + }, + { + "idiom" : "iphone", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week 5/Shimmy/week5-mission/week5-mission/Assets.xcassets/INHA-UMC-5th.imageset/INHA-UMC-5th.png b/Week 5/Shimmy/week5-mission/week5-mission/Assets.xcassets/INHA-UMC-5th.imageset/INHA-UMC-5th.png new file mode 100644 index 0000000..4c5a421 Binary files /dev/null and b/Week 5/Shimmy/week5-mission/week5-mission/Assets.xcassets/INHA-UMC-5th.imageset/INHA-UMC-5th.png differ diff --git a/Week 5/Shimmy/week5-mission/week5-mission/Main/ViewController/MainViewController.swift b/Week 5/Shimmy/week5-mission/week5-mission/Main/ViewController/MainViewController.swift new file mode 100644 index 0000000..53ac999 --- /dev/null +++ b/Week 5/Shimmy/week5-mission/week5-mission/Main/ViewController/MainViewController.swift @@ -0,0 +1,213 @@ +// +// MainViewController.swift +// week5-mission +// +// Created by Seungbo Shim on 2023/10/30. +// + +import UIKit +import SwiftUI + +class MainViewController: UIViewController { + // MARK: - Components + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.text = "INHA" + label.textAlignment = .center + label.font = UIFont.systemFont(ofSize: 28) + label.textColor = .black + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + return label + }() + + private lazy var subTitleLabel: UILabel = { + let label = UILabel() + label.text = "UMC" + label.textAlignment = .center + label.font = UIFont.boldSystemFont(ofSize: 56) + label.textColor = .black + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private lazy var logoView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "INHA-UMC-5th") + imageView.layer.cornerRadius = 100 + + imageView.clipsToBounds = true + // cornerRadius 적용시 추가 + + //imageView.layer.masksToBounds = false + // 이건 shadow 같이쓸때 추가 + // 이걸 뒤에 선언하니까 clipsToBounds가 묻힘 + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + private lazy var loginLabel: UILabel = { + let label = UILabel() + label.text = "Sign up or log in" + label.textAlignment = .center + label.font = UIFont.boldSystemFont(ofSize: 24) + label.textColor = .black + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private lazy var AppleButton: UIButton = { + let button = UIButton() + button.setTitle("Continue with Apple", for: .normal) + button.setTitleColor(.black, for: .normal) + button.backgroundColor = .white + button.addTarget(self, action: #selector(appleButtonTapped), for: .touchUpInside) + button.layer.borderColor = CGColor(red: 0, green: 0, blue: 0, alpha: 1) + button.layer.borderWidth = 2.0 + button.layer.cornerRadius = 6 + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private lazy var GoogleButton: UIButton = { + let button = UIButton() + button.setTitle("Continue with Google", for: .normal) + button.setTitleColor(.black, for: .normal) + button.backgroundColor = .white + button.addTarget(self, action: #selector(googleButtonTapped), for: .touchUpInside) + button.layer.borderColor = CGColor(red: 0, green: 0, blue: 0, alpha: 1) + button.layer.borderWidth = 2.0 + button.layer.cornerRadius = 6 + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + @objc func appleButtonTapped() { + print("Apple login") + } + + @objc func googleButtonTapped() { + print("Google login") + } + + // MARK: - Life cycle + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + setupUI() + } + + // MARK: - Set up + private func setupUI() { + self.view.backgroundColor = .white + // self.view 는 전체 view(부모) + // 전체 view에 각각의 view를 추가 + // 추가한 순서대로 z 값이 정해짐 (나중에 넣은 black이 제일 위로 감) + self.view.addSubview(titleLabel) + self.view.addSubview(subTitleLabel) + self.view.addSubview(logoView) + self.view.addSubview(loginLabel) + self.view.addSubview(AppleButton) + self.view.addSubview(GoogleButton) + // 각각 뷰에 오토레이아웃 함수 호출 + setTitleLabelLayout() + setSubTitleLabelLayout() + setLogoViewLayout() + setLoginLabelLayout() + setAppleButtonLayout() + setGoogleButtonLayout() + } + + private func setTitleLabelLayout() { + let titleLabelConstraint = [ + self.titleLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + self.titleLabel.bottomAnchor.constraint(equalTo: self.subTitleLabel.topAnchor), + ] + NSLayoutConstraint.activate(titleLabelConstraint) + } + + private func setSubTitleLabelLayout() { + let subTitleLabelConstraint = [ + self.subTitleLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + //self.subTitleLabel.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor), + self.subTitleLabel.bottomAnchor.constraint(equalTo: self.logoView.topAnchor, constant: -80) + ] + NSLayoutConstraint.activate(subTitleLabelConstraint) + } + + private func setLogoViewLayout() { + let logoViewConstant = [ + self.logoView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + self.logoView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), + self.logoView.widthAnchor.constraint(equalToConstant: 200), + self.logoView.heightAnchor.constraint(equalToConstant: 200), + ] + NSLayoutConstraint.activate(logoViewConstant) + } + + private func setLoginLabelLayout() { + let loginLabelConstraint = [ + self.loginLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + self.loginLabel.topAnchor.constraint(equalTo: self.logoView.bottomAnchor, constant: 80), + ] + NSLayoutConstraint.activate(loginLabelConstraint) + } + + private func setAppleButtonLayout() { + let appleButtonConstraint = [ + self.AppleButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + self.AppleButton.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -50), + self.AppleButton.topAnchor.constraint(equalTo: self.loginLabel.bottomAnchor, constant: 20) + ] + NSLayoutConstraint.activate(appleButtonConstraint) + } + + private func setGoogleButtonLayout() { + let googleButtonConstraint = [ + self.GoogleButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + self.GoogleButton.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -50), + self.GoogleButton.topAnchor.constraint(equalTo: self.AppleButton.bottomAnchor, constant: 10) + ] + NSLayoutConstraint.activate(googleButtonConstraint) + } +} + +// MARK: - Extension +extension UIImage { + // UIButton에 UIImage를 넣을 때, 이미지의 크기를 조정하는 함수 + func resizeImage(image: UIImage, toSize size: CGSize) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, false, 0.0) + image.draw(in: CGRect(origin: CGPoint.zero, size: size)) + let resizedImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return resizedImage + } + + // UIImage의 cornerRadius를 설정할 수 있도록 해주는 함수 + func setCornerRadius(with radius: CGFloat) -> UIImage { + let format = UIGraphicsImageRendererFormat() + format.scale = scale + let renderer = UIGraphicsImageRenderer(size: size, format: format) + return renderer.image { rendererContext in + let rect = CGRect(origin: .zero, size: size) + let path = UIBezierPath(roundedRect: rect, + byRoundingCorners: .allCorners, + cornerRadii: CGSize(width: radius, height: radius)) + path.close() + + let cgContext = rendererContext.cgContext + cgContext.saveGState() + path.addClip() + draw(in: rect) + cgContext.restoreGState() + } + } +} + + +@available(iOS 13.0.0, *) +struct MainViewControllerPreview: PreviewProvider { + static var previews: some View { + MainViewController().toPreview() + } +} diff --git a/Week 5/Shimmy/week5-mission/week5-mission/Menu/View/UniversityCell.swift b/Week 5/Shimmy/week5-mission/week5-mission/Menu/View/UniversityCell.swift new file mode 100644 index 0000000..bad224d --- /dev/null +++ b/Week 5/Shimmy/week5-mission/week5-mission/Menu/View/UniversityCell.swift @@ -0,0 +1,10 @@ +// +// UniversityCell.swift +// week5-mission +// +// Created by Seungbo Shim on 2023/10/31. +// + +import UIKit + + diff --git a/Week 5/Shimmy/week5-mission/week5-mission/Menu/ViewController/MenuViewController.swift b/Week 5/Shimmy/week5-mission/week5-mission/Menu/ViewController/MenuViewController.swift new file mode 100644 index 0000000..c60f560 --- /dev/null +++ b/Week 5/Shimmy/week5-mission/week5-mission/Menu/ViewController/MenuViewController.swift @@ -0,0 +1,167 @@ +// +// MenuViewController.swift +// week5-mission +// +// Created by Seungbo Shim on 2023/10/31. +// + +import UIKit +import SwiftUI + +// MARK: - Cell +class UniversityCell: UICollectionViewCell { + static let reuseIdentifier = "UniversityCell" + + // MARK: - UI Components + private lazy var universityUIButton: UIButton = { + var config = UIButton.Configuration.filled() + config.title = "OO대" + config.baseForegroundColor = .black + config.baseBackgroundColor = .white + config.background.strokeWidth = 1.0 + config.background.strokeColor = .white + config.cornerStyle = .fixed + config.titleAlignment = .center + + if let image = UIImage(named: "INHA-UMC-5th") { + let resizeImage = image.resizeImage(image: image, toSize: CGSize(width: 150, height: 150)) + config.image = resizeImage?.setCornerRadius(with: 15) + config.imagePlacement = .top + config.imagePadding = 10 + } + + let button = UIButton(configuration: config, primaryAction: nil) + button.addTarget(self, action: #selector(ButtonTapped), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + + return button + }() + + @objc func ButtonTapped() { + print("뿅") + } + + // MARK: - Configure + public func configure() { + + self.setupUI() + } + + // MARK: - UI Setup + private func setupUI() { + self.addSubview(self.universityUIButton) + } +} + + +class MenuViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + // MARK: - Components + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.text = "GACI 지부" + label.textAlignment = .center + label.font = UIFont.boldSystemFont(ofSize: 32) + label.textColor = .black + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + return label + }() + + + private lazy var uiCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .white + collectionView.dataSource = self + collectionView.delegate = self + collectionView.register(UniversityCell.self, forCellWithReuseIdentifier: UniversityCell.reuseIdentifier) + collectionView.translatesAutoresizingMaskIntoConstraints = false + + return collectionView + }() + + // MARK: - UICollectionViewDataSource 메서드 + // 특정 섹션에 속하는 아이템(셀)의 총 개수를 반환한다. + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 4 + } + + // 각 셀을 생성하고 구성하기 위해 호출 + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = uiCollectionView.dequeueReusableCell(withReuseIdentifier: UniversityCell.reuseIdentifier, for: indexPath) as? UniversityCell else { + fatalError("Failed to dequeue UniversityCell in CalcController") + } + cell.configure() + + return cell + } + + // MARK: - UICollectionViewDelegateFlowLayout + + // CollectionView에 들어갈 Item에 size에 대한 정보 + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: self.view.bounds.width / 4 + 76, height: self.view.bounds.width / 2) + } + + // CollectionView에 들어갈 셀 사이의 minimum spacing에 대한 정보 + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 50 + } + + // CollectionView에 들어갈 각 Item의 Inset(여백) 대한 정보 + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 0 + } + + // MARK: - Life cycle + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + setupUI() + } + + // MARK: - Set up + private func setupUI() { + self.view.backgroundColor = .white + // self.view 는 전체 view(부모) + // 전체 view에 각각의 view를 추가 + // 추가한 순서대로 z 값이 정해짐 (나중에 넣은 black이 제일 위로 감) + self.view.addSubview(titleLabel) + self.view.addSubview(uiCollectionView) + + // 각각 뷰에 오토레이아웃 함수 호출 + setTitleLabelLayout() + setUICollectionViewLayout() + + } + + private func setTitleLabelLayout() { + let titleLabelConstraint = [ + self.titleLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + self.titleLabel.bottomAnchor.constraint(equalTo: self.uiCollectionView.topAnchor, constant: -100), + ] + NSLayoutConstraint.activate(titleLabelConstraint) + } + + + + private func setUICollectionViewLayout() { + let uiCollectionViewContraint = [ + self.uiCollectionView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), + self.uiCollectionView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), + // 밑에 제약조건 안넣으니까 안떴음;; + self.uiCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9), + self.uiCollectionView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.6) + ] + NSLayoutConstraint.activate(uiCollectionViewContraint) + } +} + + +@available(iOS 13.0.0, *) +struct MenuViewControllerPreview: PreviewProvider { + static var previews: some View { + MenuViewController().toPreview() + } +} diff --git a/Week 5/Shimmy/week5-mission/week5-mission/SceneDelegate.swift b/Week 5/Shimmy/week5-mission/week5-mission/SceneDelegate.swift index ccbe42a..fcfbc65 100644 --- a/Week 5/Shimmy/week5-mission/week5-mission/SceneDelegate.swift +++ b/Week 5/Shimmy/week5-mission/week5-mission/SceneDelegate.swift @@ -13,11 +13,27 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let windowScene = (scene as? UIWindowScene) else { return } + + let window = UIWindow(windowScene: windowScene) + + // 처음 보일 main ViewController + let rootViewController = MainViewController() + + // NavigationController 설정 + let navigationController = UINavigationController(rootViewController: rootViewController) + + // 위에서 만든 viewController를 첫 화면으로 설정(navigationController로 설정 + window.rootViewController = navigationController + + // 화면에 보이게 설정 + window.makeKeyAndVisible() + + self.window = window + } func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. diff --git a/Week 5/Shimmy/week5-practice/week5-practice/WorkbookViewController.swift b/Week 5/Shimmy/week5-practice/week5-practice/WorkbookViewController.swift index fb20d78..0f0a1f9 100644 --- a/Week 5/Shimmy/week5-practice/week5-practice/WorkbookViewController.swift +++ b/Week 5/Shimmy/week5-practice/week5-practice/WorkbookViewController.swift @@ -148,3 +148,4 @@ struct WorkbookViewControllerPreview: PreviewProvider { WorkbookViewController().toPreview() } } + diff --git a/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.pbxproj b/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c7f644b --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.pbxproj @@ -0,0 +1,643 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 03474F362AF9487C00532F6F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F352AF9487C00532F6F /* AppDelegate.swift */; }; + 03474F382AF9487C00532F6F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F372AF9487C00532F6F /* SceneDelegate.swift */; }; + 03474F3A2AF9487C00532F6F /* CalcController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F392AF9487C00532F6F /* CalcController.swift */; }; + 03474F3F2AF9487D00532F6F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03474F3E2AF9487D00532F6F /* Assets.xcassets */; }; + 03474F422AF9487D00532F6F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 03474F402AF9487D00532F6F /* LaunchScreen.storyboard */; }; + 03474F4D2AF9487D00532F6F /* week6_missionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F4C2AF9487D00532F6F /* week6_missionTests.swift */; }; + 03474F572AF9487D00532F6F /* week6_missionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F562AF9487D00532F6F /* week6_missionUITests.swift */; }; + 03474F592AF9487D00532F6F /* week6_missionUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F582AF9487D00532F6F /* week6_missionUITestsLaunchTests.swift */; }; + 03474F692AF9506100532F6F /* CalcControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F682AF9506100532F6F /* CalcControllerViewModel.swift */; }; + 03474F6B2AF9509100532F6F /* ButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F6A2AF9509100532F6F /* ButtonCell.swift */; }; + 03474F6D2AF950CA00532F6F /* CalcHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F6C2AF950CA00532F6F /* CalcHeaderCell.swift */; }; + 03474F6F2AF950F900532F6F /* CalculatorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03474F6E2AF950F900532F6F /* CalculatorButton.swift */; }; + 03F9FC972B02BC9A00FA3DCA /* CalculatorOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F9FC962B02BC9A00FA3DCA /* CalculatorOperation.swift */; }; + 03F9FC992B02C04F00FA3DCA /* String+Extention.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F9FC982B02C04F00FA3DCA /* String+Extention.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 03474F492AF9487D00532F6F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 03474F2A2AF9487C00532F6F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 03474F312AF9487C00532F6F; + remoteInfo = "week6-mission"; + }; + 03474F532AF9487D00532F6F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 03474F2A2AF9487C00532F6F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 03474F312AF9487C00532F6F; + remoteInfo = "week6-mission"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 03474F322AF9487C00532F6F /* week6-mission.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "week6-mission.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 03474F352AF9487C00532F6F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 03474F372AF9487C00532F6F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 03474F392AF9487C00532F6F /* CalcController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalcController.swift; sourceTree = ""; }; + 03474F3E2AF9487D00532F6F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 03474F412AF9487D00532F6F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 03474F432AF9487D00532F6F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 03474F482AF9487D00532F6F /* week6-missionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "week6-missionTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 03474F4C2AF9487D00532F6F /* week6_missionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = week6_missionTests.swift; sourceTree = ""; }; + 03474F522AF9487D00532F6F /* week6-missionUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "week6-missionUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 03474F562AF9487D00532F6F /* week6_missionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = week6_missionUITests.swift; sourceTree = ""; }; + 03474F582AF9487D00532F6F /* week6_missionUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = week6_missionUITestsLaunchTests.swift; sourceTree = ""; }; + 03474F682AF9506100532F6F /* CalcControllerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalcControllerViewModel.swift; sourceTree = ""; }; + 03474F6A2AF9509100532F6F /* ButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCell.swift; sourceTree = ""; }; + 03474F6C2AF950CA00532F6F /* CalcHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalcHeaderCell.swift; sourceTree = ""; }; + 03474F6E2AF950F900532F6F /* CalculatorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalculatorButton.swift; sourceTree = ""; }; + 03F9FC962B02BC9A00FA3DCA /* CalculatorOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalculatorOperation.swift; sourceTree = ""; }; + 03F9FC982B02C04F00FA3DCA /* String+Extention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extention.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 03474F2F2AF9487C00532F6F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03474F452AF9487D00532F6F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03474F4F2AF9487D00532F6F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 03474F292AF9487C00532F6F = { + isa = PBXGroup; + children = ( + 03474F342AF9487C00532F6F /* week6-mission */, + 03474F4B2AF9487D00532F6F /* week6-missionTests */, + 03474F552AF9487D00532F6F /* week6-missionUITests */, + 03474F332AF9487C00532F6F /* Products */, + ); + sourceTree = ""; + }; + 03474F332AF9487C00532F6F /* Products */ = { + isa = PBXGroup; + children = ( + 03474F322AF9487C00532F6F /* week6-mission.app */, + 03474F482AF9487D00532F6F /* week6-missionTests.xctest */, + 03474F522AF9487D00532F6F /* week6-missionUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 03474F342AF9487C00532F6F /* week6-mission */ = { + isa = PBXGroup; + children = ( + 03474F392AF9487C00532F6F /* CalcController.swift */, + 03474F682AF9506100532F6F /* CalcControllerViewModel.swift */, + 03F9FC982B02C04F00FA3DCA /* String+Extention.swift */, + 03474F672AF94FCE00532F6F /* Views */, + 03474F662AF94FC500532F6F /* Models */, + 03474F652AF94F9400532F6F /* Supporting */, + 03474F432AF9487D00532F6F /* Info.plist */, + ); + path = "week6-mission"; + sourceTree = ""; + }; + 03474F4B2AF9487D00532F6F /* week6-missionTests */ = { + isa = PBXGroup; + children = ( + 03474F4C2AF9487D00532F6F /* week6_missionTests.swift */, + ); + path = "week6-missionTests"; + sourceTree = ""; + }; + 03474F552AF9487D00532F6F /* week6-missionUITests */ = { + isa = PBXGroup; + children = ( + 03474F562AF9487D00532F6F /* week6_missionUITests.swift */, + 03474F582AF9487D00532F6F /* week6_missionUITestsLaunchTests.swift */, + ); + path = "week6-missionUITests"; + sourceTree = ""; + }; + 03474F652AF94F9400532F6F /* Supporting */ = { + isa = PBXGroup; + children = ( + 03474F352AF9487C00532F6F /* AppDelegate.swift */, + 03474F372AF9487C00532F6F /* SceneDelegate.swift */, + 03474F3E2AF9487D00532F6F /* Assets.xcassets */, + 03474F402AF9487D00532F6F /* LaunchScreen.storyboard */, + ); + path = Supporting; + sourceTree = ""; + }; + 03474F662AF94FC500532F6F /* Models */ = { + isa = PBXGroup; + children = ( + 03474F6E2AF950F900532F6F /* CalculatorButton.swift */, + 03F9FC962B02BC9A00FA3DCA /* CalculatorOperation.swift */, + ); + path = Models; + sourceTree = ""; + }; + 03474F672AF94FCE00532F6F /* Views */ = { + isa = PBXGroup; + children = ( + 03474F6A2AF9509100532F6F /* ButtonCell.swift */, + 03474F6C2AF950CA00532F6F /* CalcHeaderCell.swift */, + ); + path = Views; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 03474F312AF9487C00532F6F /* week6-mission */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03474F5C2AF9487D00532F6F /* Build configuration list for PBXNativeTarget "week6-mission" */; + buildPhases = ( + 03474F2E2AF9487C00532F6F /* Sources */, + 03474F2F2AF9487C00532F6F /* Frameworks */, + 03474F302AF9487C00532F6F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "week6-mission"; + productName = "week6-mission"; + productReference = 03474F322AF9487C00532F6F /* week6-mission.app */; + productType = "com.apple.product-type.application"; + }; + 03474F472AF9487D00532F6F /* week6-missionTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03474F5F2AF9487D00532F6F /* Build configuration list for PBXNativeTarget "week6-missionTests" */; + buildPhases = ( + 03474F442AF9487D00532F6F /* Sources */, + 03474F452AF9487D00532F6F /* Frameworks */, + 03474F462AF9487D00532F6F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 03474F4A2AF9487D00532F6F /* PBXTargetDependency */, + ); + name = "week6-missionTests"; + productName = "week6-missionTests"; + productReference = 03474F482AF9487D00532F6F /* week6-missionTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 03474F512AF9487D00532F6F /* week6-missionUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03474F622AF9487D00532F6F /* Build configuration list for PBXNativeTarget "week6-missionUITests" */; + buildPhases = ( + 03474F4E2AF9487D00532F6F /* Sources */, + 03474F4F2AF9487D00532F6F /* Frameworks */, + 03474F502AF9487D00532F6F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 03474F542AF9487D00532F6F /* PBXTargetDependency */, + ); + name = "week6-missionUITests"; + productName = "week6-missionUITests"; + productReference = 03474F522AF9487D00532F6F /* week6-missionUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 03474F2A2AF9487C00532F6F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + TargetAttributes = { + 03474F312AF9487C00532F6F = { + CreatedOnToolsVersion = 14.3.1; + }; + 03474F472AF9487D00532F6F = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 03474F312AF9487C00532F6F; + }; + 03474F512AF9487D00532F6F = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 03474F312AF9487C00532F6F; + }; + }; + }; + buildConfigurationList = 03474F2D2AF9487C00532F6F /* Build configuration list for PBXProject "week6-mission" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 03474F292AF9487C00532F6F; + productRefGroup = 03474F332AF9487C00532F6F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 03474F312AF9487C00532F6F /* week6-mission */, + 03474F472AF9487D00532F6F /* week6-missionTests */, + 03474F512AF9487D00532F6F /* week6-missionUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 03474F302AF9487C00532F6F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03474F422AF9487D00532F6F /* LaunchScreen.storyboard in Resources */, + 03474F3F2AF9487D00532F6F /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03474F462AF9487D00532F6F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03474F502AF9487D00532F6F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 03474F2E2AF9487C00532F6F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03474F3A2AF9487C00532F6F /* CalcController.swift in Sources */, + 03474F6F2AF950F900532F6F /* CalculatorButton.swift in Sources */, + 03474F362AF9487C00532F6F /* AppDelegate.swift in Sources */, + 03474F382AF9487C00532F6F /* SceneDelegate.swift in Sources */, + 03F9FC992B02C04F00FA3DCA /* String+Extention.swift in Sources */, + 03474F6D2AF950CA00532F6F /* CalcHeaderCell.swift in Sources */, + 03474F6B2AF9509100532F6F /* ButtonCell.swift in Sources */, + 03474F692AF9506100532F6F /* CalcControllerViewModel.swift in Sources */, + 03F9FC972B02BC9A00FA3DCA /* CalculatorOperation.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03474F442AF9487D00532F6F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03474F4D2AF9487D00532F6F /* week6_missionTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03474F4E2AF9487D00532F6F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03474F592AF9487D00532F6F /* week6_missionUITestsLaunchTests.swift in Sources */, + 03474F572AF9487D00532F6F /* week6_missionUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 03474F4A2AF9487D00532F6F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 03474F312AF9487C00532F6F /* week6-mission */; + targetProxy = 03474F492AF9487D00532F6F /* PBXContainerItemProxy */; + }; + 03474F542AF9487D00532F6F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 03474F312AF9487C00532F6F /* week6-mission */; + targetProxy = 03474F532AF9487D00532F6F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 03474F402AF9487D00532F6F /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 03474F412AF9487D00532F6F /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 03474F5A2AF9487D00532F6F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 03474F5B2AF9487D00532F6F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 03474F5D2AF9487D00532F6F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "week6-mission/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.week6-mission"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 03474F5E2AF9487D00532F6F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "week6-mission/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.week6-mission"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 03474F602AF9487D00532F6F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.week6-missionTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/week6-mission.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/week6-mission"; + }; + name = Debug; + }; + 03474F612AF9487D00532F6F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.week6-missionTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/week6-mission.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/week6-mission"; + }; + name = Release; + }; + 03474F632AF9487D00532F6F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.week6-missionUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "week6-mission"; + }; + name = Debug; + }; + 03474F642AF9487D00532F6F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.week6-missionUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "week6-mission"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 03474F2D2AF9487C00532F6F /* Build configuration list for PBXProject "week6-mission" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03474F5A2AF9487D00532F6F /* Debug */, + 03474F5B2AF9487D00532F6F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 03474F5C2AF9487D00532F6F /* Build configuration list for PBXNativeTarget "week6-mission" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03474F5D2AF9487D00532F6F /* Debug */, + 03474F5E2AF9487D00532F6F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 03474F5F2AF9487D00532F6F /* Build configuration list for PBXNativeTarget "week6-missionTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03474F602AF9487D00532F6F /* Debug */, + 03474F612AF9487D00532F6F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 03474F622AF9487D00532F6F /* Build configuration list for PBXNativeTarget "week6-missionUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03474F632AF9487D00532F6F /* Debug */, + 03474F642AF9487D00532F6F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 03474F2A2AF9487C00532F6F /* Project object */; +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcschemes/xcschememanagement.plist b/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..5b0ec06 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + week6-mission.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Week 6/Shimmy/week6-mission/week6-mission/CalcController.swift b/Week 6/Shimmy/week6-mission/week6-mission/CalcController.swift new file mode 100644 index 0000000..65370d6 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/CalcController.swift @@ -0,0 +1,165 @@ +// +// ViewController.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/07. +// + +import UIKit + +class CalcController: UIViewController { + + let viewModel: CalcControllerViewModel + + // MARK: - UI Components + private let collectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .black + + // 등록할 뷰의 종류 지정 + collectionView.register(CalcHeaderCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: CalcHeaderCell.reuseIdentifier) + collectionView.register(ButtonCell.self, forCellWithReuseIdentifier: ButtonCell.identifier) + + collectionView.translatesAutoresizingMaskIntoConstraints = false + return collectionView + }() + + + // MARK: - Lifecycle + init(viewModel: CalcControllerViewModel = CalcControllerViewModel()) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .systemPurple + self.setupUI() + + self.collectionView.delegate = self + self.collectionView.dataSource = self + + self.viewModel.updateViews = { + [weak self] in + DispatchQueue.main.async { + [weak self] in + self?.collectionView.reloadData() + } + } + } + + // MARK: = UI Setup + private func setupUI() { + self.view.addSubview(self.collectionView) + + + setCollectionViewLayout() + } + + private func setCollectionViewLayout() { + let collectionViewConstraint = [ + self.collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + self.collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), + self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor), + self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), + ] + NSLayoutConstraint.activate(collectionViewConstraint) + } +} + +// MARK: - CollectionView Methods +extension CalcController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + // MARK: - Section Header Cell + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalcHeaderCell.reuseIdentifier, for: indexPath) as? CalcHeaderCell else { + fatalError("Failed to dequeue CalcHeaderCell in CalcController") + } + header.configure(currentCalcText: self.viewModel.calcHeaderLabel) + + return header + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { + + + // Cell Spacing + let totalCellHeight = view.frame.size.width + let totalVerticalCellSpacing = CGFloat(10*4) + + // Screen height + let window = view.window?.windowScene?.keyWindow + let topPadding = window?.safeAreaInsets.top ?? 0 + let bottomPadding = window?.safeAreaInsets.bottom ?? 0 + + // (상단, 하단의 safeArea를 포함하는 padding은 제외한)사용 가능한 뷰의 height + let avaliableScreenHeight = view.frame.size.height - topPadding - bottomPadding + + // Calculate Header Height + let headerHeight = (avaliableScreenHeight - totalCellHeight) - totalVerticalCellSpacing + + return CGSize(width: view.frame.size.width, height: headerHeight) + } + + // MARK: - Normal Cells (Buttons) + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.viewModel.calcButtonCells.count + } + + // 각 셀의 생성, 구성 + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ButtonCell.identifier, for: indexPath) as? ButtonCell else { + fatalError("Failed to dequeue ButtonCell in CalcController.") + } + let calcButton = self.viewModel.calcButtonCells[indexPath.row] + + cell.configure(with: calcButton) + + // 연산자의 색상 변경? + if let operation = self.viewModel.operation, self.viewModel.secondNumber == nil { + if operation.title == calcButton.title { + cell.setOperationSelected() + } + } + + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + + let calcButton = self.viewModel.calcButtonCells[indexPath.row] + + // 0 버튼의 사이즈만 길게 + switch calcButton { + case let .number(int) where int == 0: + return CGSize( + width: (view.frame.self.width/5)*2 + ((view.frame.self.width/5)/3), + height: view.frame.size.width/5 + ) + default: + return CGSize(width: view.frame.size.width/5, height: view.frame.size.width/5) + } + } + + // 버튼의 배치?? + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return (self.view.frame.width/5)/4 + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let buttonCell = self.viewModel.calcButtonCells[indexPath.row] + self.viewModel.didSelectButton(with: buttonCell) + } +} + diff --git a/Week 6/Shimmy/week6-mission/week6-mission/CalcControllerViewModel.swift b/Week 6/Shimmy/week6-mission/week6-mission/CalcControllerViewModel.swift new file mode 100644 index 0000000..1d96a4c --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/CalcControllerViewModel.swift @@ -0,0 +1,259 @@ +// +// CalcControllerViewModel.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/07. +// + +import Foundation + +enum CurrentNumber { + case firstNumber + case secondNumber +} + +class CalcControllerViewModel { + var updateViews: (()->Void)? + + // MARK: - TableView DataSource Array + // 계산기 버튼 위치 + // Model(CalculatorButton)의 정보(text, color)를 View(ButtonCell)에 가져오고, ViewModel에서 이 위치에 따라 배치 + let calcButtonCells: [CalculatorButton] = [ + .allClear, .plusMinus, .percentage, .divide, + .number(7), .number(8), .number(9), .multiply, + .number(4), .number(5), .number(6), .subtract, + .number(3), .number(2), .number(1), .add, + .number(0), .decimal, .equals + ] + + // private(set) : 해당 변수의 setter를 private으로 지정 + private(set) lazy var calcHeaderLabel: String = self.firstNumber ?? "0" + private(set) var currentNumber: CurrentNumber = .firstNumber + + private(set) var firstNumber: String? = nil { + didSet { + self.calcHeaderLabel = self.firstNumber?.description ?? "0" + } + } + private(set) var secondNumber: String? = nil { + didSet { + self.calcHeaderLabel = self.secondNumber?.description ?? "0" + } + } + private(set) var operation: CalculatorOperation? = nil + private(set) var firstNumberIsDecimal: Bool = false + private(set) var secondNumberIsDecimal: Bool = false + + var eitherNumberIsDecimal: Bool { + return firstNumberIsDecimal || secondNumberIsDecimal + } + + // MARK: - Memory Variables + // "=" 누르면 이전 숫자가 반복되어 연산 + private(set) var prevNumber: String? = nil + private(set) var prevOperation: CalculatorOperation? = nil + + // MARK: - Businiss Logic + public func didSelectButton(with calcButton: CalculatorButton) { + switch calcButton { + case .allClear: self.didSelectAllclear() + case .plusMinus: self.didSelectPlusMinus() + case .percentage: self.didSelectPercentage() + case .divide: self.didSelectOperation(with: .divide) + case .multiply: self.didSelectOperation(with: .multiply) + case .subtract: self.didSelectOperation(with: .subtract) + case .add: self.didSelectOperation(with: .add) + case .equals: self.didSelectedEqualsButton() + case .number(let number): self.didSelectNumber(with: number) + case .decimal: self.didSelectDecimal() + } + + self.updateViews?() + } + + // MARK: - All Clear + private func didSelectAllclear() { + self.calcHeaderLabel = "0" + self.currentNumber = .firstNumber + self.firstNumber = nil + self.secondNumber = nil + self.operation = nil + self.firstNumberIsDecimal = false + self.secondNumberIsDecimal = false + self.prevNumber = nil + self.prevOperation = nil + } + + // MARK: - Selecting Numbers + private func didSelectNumber(with number: Int) { + + if self.currentNumber == .firstNumber { + // if let -> 상수로 옵셔널 바인딩, if let -> 변수로 옵셔널 바인딩 + if var firstNumber = self.firstNumber { + // 기존에 입력되어있던 firstNumber에 새로 입력한 (파라미터의)number를 뒤에 이어서 붙여주는 동작 + firstNumber.append(number.description) + self.firstNumber = firstNumber + self.prevNumber = firstNumber + } else { + // 기존에 아무 것도 입력되어있지 않다면, number를 String으로 변환하여 firstNumber에 넣어준다. + self.firstNumber = number.description + self.prevNumber = number.description + } + } else { + if var secondNumber = self.secondNumber { + // 기존에 입력되어있던 secondNumber에 새로 입력한 (파라미터의)number를 뒤에 이어서 붙여주는 동작 + secondNumber.append(number.description) + self.secondNumber = secondNumber + self.secondNumber = secondNumber + } else { + // 기존에 아무 것도 입력되어있지 않다면, number를 String으로 변환하여 secondNumber에 넣어준다. + self.secondNumber = number.description + self.prevNumber = number.description + } + } + } + + // MARK: - Equals & ArithmeticOperations + + private func didSelectedEqualsButton() { + + if let operation = self.operation, + let firstNumber = self.firstNumber?.toDouble, + // secondNumber가 존재하는 경우 + let secondNumber = self.secondNumber?.toDouble { + + // firstNumber와 secondNumber 다음에 정상적으로 Equals가 눌러지는 경우 + let result = self.getOperationResult(operation, firstNumber, secondNumber) + // firstNumber과 secondNumber가 둘 다 Decimal(소수)라면 그대로 출력하고, 그렇지 않으면 Int로 변환하여 출력한다. + let resultString = self.eitherNumberIsDecimal ? result.description : result.toInt?.description + + self.secondNumber = nil + self.prevOperation = operation + self.operation = nil + self.firstNumber = resultString + self.currentNumber = .firstNumber + // secondNumberrk 존재하지 않는 경우 -> firstNumber와 prevOperation을 가지고 계산한다. + } else if let prevOperation = self.prevOperation, + let firstNumber = self.firstNumber?.toDouble, + // 이전에 입력된 숫자가 없는 경우 + let prevNumber = self.prevNumber?.toDouble { + + // firstNumber와 operation을 기반한 연산으로 result가 업데이트된다. + let result = self.getOperationResult(prevOperation, firstNumber, prevNumber) + let resultString = self.eitherNumberIsDecimal ? result.description : result.toInt?.description + self.firstNumber = resultString + } + } + + private func didSelectOperation(with operation: CalculatorOperation) { + // firstNumber와 operation만 입력된 상태일 때 + if self.currentNumber == .firstNumber { + self.operation = operation + self.currentNumber = .secondNumber + // firstNumber, operation, secondNumber까지 입력된 상태일 때 -> 새로운 operation이 들어오면 이전의 결과값을 출력해준다. + } else if self.currentNumber == .secondNumber { + if let prevOperation = self.operation, + let firstNumber = self.firstNumber?.toDouble, + let secondNumber = self.secondNumber?.toDouble { + // 이후 추가로 연산자가 들어올 경우 이전 값을 출력해주고 새로운 연산자를 받아야 하는데, 출력 값은 이전에 입력받았던 연산자를 사용해야하므로, opearion이 아닌 prevOperation을 사용한다. + let result = self.getOperationResult(prevOperation, firstNumber, secondNumber) + let resultString = self.eitherNumberIsDecimal ? result.description : result.toInt?.description + self.secondNumber = nil + self.firstNumber = resultString + self.currentNumber = .secondNumber + self.operation = operation + } else { + // secondNumber가 없으면 operation만 새로 갱신해준다. + self.operation = operation + } + } + } + + // MARK: - Helper + private func getOperationResult(_ operation: CalculatorOperation, _ firstNumber: Double?, _ secondNumber: Double?) -> Double { + guard let firstNumber = firstNumber, + let secondNumber = secondNumber else { return 0 } + switch operation { + case .divide: + return (firstNumber / secondNumber) + case .multiply: + return (firstNumber * secondNumber) + case .subtract: + return (firstNumber - secondNumber) + case .add: + return (firstNumber + secondNumber) + + } + } + + // MARK: - Action Buttons + private func didSelectPlusMinus() { + // firstNumber만 입력된 경우 + if self.currentNumber == .firstNumber, var number = self.firstNumber { + if number .contains("-") { + number.removeFirst() + } else { + number.insert("-", at: number.startIndex) + } + + self.firstNumber = number + self.prevNumber = number + } else if self.currentNumber == .secondNumber, var number = self.secondNumber { + // secondNumber까지 입력된 경우 + if number .contains("-") { + number.removeFirst() + } else { + number.insert("-", at: number.startIndex) + } + + self.secondNumber = number + self.prevNumber = number + } + } + + private func didSelectPercentage() { + + if self.currentNumber == .firstNumber, var number = self.firstNumber?.toDouble { + number /= 100 + if number.isInteger { + self.firstNumber = number.toInt?.description + } else { + self.firstNumber = number.description + self.firstNumberIsDecimal = true + } + } else if self.currentNumber == .secondNumber, var number = self.secondNumber?.toDouble { + + number /= 100 + if number.isInteger { + self.secondNumber = number.toInt?.description + } else { + self.secondNumber = number.description + self.secondNumberIsDecimal = true + } + } + } + + private func didSelectDecimal() { + + if self.currentNumber == .firstNumber { + self.firstNumberIsDecimal = true + + if let firstNumber = self.firstNumber, !firstNumber.contains(".") { + self.firstNumber = firstNumber.appending(".") + + } else if self.firstNumber == nil { + self.firstNumber = "0." + } + } else if self.currentNumber == .secondNumber { + self.secondNumberIsDecimal = true + + if let secondNumber = self.secondNumber, !secondNumber.contains(".") { + self.secondNumber = secondNumber.appending(".") + + } else if self.secondNumber == nil { + self.secondNumber = "0." + } + } + } + +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Info.plist b/Week 6/Shimmy/week6-mission/week6-mission/Info.plist new file mode 100644 index 0000000..0eb786d --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Info.plist @@ -0,0 +1,23 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Models/CalculatorButton.swift b/Week 6/Shimmy/week6-mission/week6-mission/Models/CalculatorButton.swift new file mode 100644 index 0000000..f843ffe --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Models/CalculatorButton.swift @@ -0,0 +1,92 @@ +// +// CalculaterButton.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/07. +// + +import UIKit + +enum CalculatorButton { + case allClear + case plusMinus + case percentage + case divide + case multiply + case subtract + case add + case equals + case number(Int) + case decimal + + init(calcButton: CalculatorButton) { + switch calcButton { + + case .allClear, .plusMinus, .percentage, .divide, .multiply, .subtract, .add, .equals, .decimal: + self = calcButton + case .number(let int): + if int.description.count == 1 { + self = calcButton + } else { + fatalError("CalculatorButton.number Int was not 1 digit during init") + } + } + } +} + +extension CalculatorButton { + // 버튼 글자 + var title: String { + switch self { + case .allClear: + return "AC" + case .plusMinus: + return "+/-" + case .percentage: + return "%" + case .divide: + return "/" + case .multiply: + return "x" + case .subtract: + return "-" + case .add: + return "+" + case .equals: + return "=" + case .number(let int): + return int.description + case .decimal: + return "." + } + } + + enum Color: String { + case lightGray + case systemOrange + case darkGray + case white + } + + // 원래 버튼 색 + var color: UIColor { + switch self { + case .allClear, .plusMinus, .percentage: + return .lightGray + case .divide, .multiply, .subtract, .add, .equals: + return .systemOrange + case .number, .decimal: + return .darkGray + } + } + + // 누르면 바뀌는 색 + var selectedColor: UIColor? { + switch self { + case .allClear, .plusMinus, .percentage, .equals, .number, .decimal: + return nil + case .divide, .multiply, .subtract, .add: + return .white + } + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Models/CalculatorOperation.swift b/Week 6/Shimmy/week6-mission/week6-mission/Models/CalculatorOperation.swift new file mode 100644 index 0000000..c498ae6 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Models/CalculatorOperation.swift @@ -0,0 +1,28 @@ +// +// CalculatorOperation.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/14. +// + +import Foundation + +enum CalculatorOperation { + case divide + case multiply + case subtract + case add + + var title: String { + switch self { + case .divide: + return "/" + case .multiply: + return "x" + case .subtract: + return "-" + case .add: + return "+" + } + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission/String+Extention.swift b/Week 6/Shimmy/week6-mission/week6-mission/String+Extention.swift new file mode 100644 index 0000000..316edf2 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/String+Extention.swift @@ -0,0 +1,26 @@ +// +// String+Extention.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/14. +// + +import Foundation + +extension String { + var toDouble: Double? { + return Double(self) + } +} + +extension Double { + var toInt: Int? { + return Int(self) + } +} + +extension FloatingPoint { + var isInteger: Bool { + return rounded() == self + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Supporting/AppDelegate.swift b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/AppDelegate.swift new file mode 100644 index 0000000..5abb53f --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/07. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/AccentColor.colorset/Contents.json b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/AppIcon.appiconset/Contents.json b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/Contents.json b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Base.lproj/LaunchScreen.storyboard b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Supporting/SceneDelegate.swift b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/SceneDelegate.swift new file mode 100644 index 0000000..111912d --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Supporting/SceneDelegate.swift @@ -0,0 +1,68 @@ +// +// SceneDelegate.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/07. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let windowScene = (scene as? UIWindowScene) else { return } + + let window = UIWindow(windowScene: windowScene) + + // 처음 보일 main ViewController + let rootViewController = CalcController() + + // NavigationController 설정 + let navigationController = UINavigationController(rootViewController: rootViewController) + + // 위에서 만든 viewController를 첫 화면으로 설정(navigationController로 설정 + window.rootViewController = navigationController + + // 화면에 보이게 설정 + window.makeKeyAndVisible() + + self.window = window + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Views/ButtonCell.swift b/Week 6/Shimmy/week6-mission/week6-mission/Views/ButtonCell.swift new file mode 100644 index 0000000..dabd273 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Views/ButtonCell.swift @@ -0,0 +1,69 @@ +// +// ButtonCell.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/07. +// + +import UIKit + +class ButtonCell: UICollectionViewCell { + static let identifier = "ButtonCell" + + // MARK: - Variables + private(set) var calculatorButton: CalculatorButton! + + // MARK: - UI Components + private let titleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.font = .systemFont(ofSize: 40, weight: .regular) + label.text = "Error" + return label + }() + + // MARK: - Configure + public func configure(with calculatorButton: CalculatorButton) { + // calculatorButton 모델에서 불러옴 + self.calculatorButton = calculatorButton + + // calculatorButton의 title을 titleLabel의 text로 + self.titleLabel.text = calculatorButton.title + // color를 backgroundColor로 + self.backgroundColor = calculatorButton.color + self.titleLabel.textColor = .white + + self.setupUI() + } + + public func setOperationSelected() { + self.titleLabel.textColor = .orange + self.backgroundColor = .white + } + + private func setupUI() { + self.addSubview(titleLabel) + titleLabel.translatesAutoresizingMaskIntoConstraints = false + + // 버튼 모양 둥글게 cornerRadius 조절 (0버튼만 다르게) + switch self.calculatorButton { + case let .number(int) where int == 0: + self.layer.cornerRadius = 36 + + default: + self.layer.cornerRadius = self.frame.size.width/2 + } + + // titleLable 위치를 각 버튼의 가운데로 설정 + NSLayoutConstraint.activate([ + self.titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor), + self.titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.titleLabel.heightAnchor.constraint(equalTo: self.heightAnchor), + self.titleLabel.widthAnchor.constraint(equalTo: self.widthAnchor), + ]) + } + + override func prepareForReuse() { + + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-mission/Views/CalcHeaderCell.swift b/Week 6/Shimmy/week6-mission/week6-mission/Views/CalcHeaderCell.swift new file mode 100644 index 0000000..ad5eced --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-mission/Views/CalcHeaderCell.swift @@ -0,0 +1,56 @@ +// +// CalcHeaderCell.swift +// week6-mission +// +// Created by Seungbo Shim on 2023/11/07. +// + +import UIKit + +class CalcHeaderCell: UICollectionReusableView { + static let reuseIdentifier = "CalcHeaderCell" + + // MARK: - UI Components + private let label: UILabel = { + let label = UILabel() + label.textColor = .white + label.textAlignment = .right + label.font = .systemFont(ofSize: 72, weight: .regular) + label.text = "Error" + label.translatesAutoresizingMaskIntoConstraints = false + + return label + }() + + // MARK: - LifeCylcle + override init(frame: CGRect) { + super.init(frame: frame) + self.setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // 현재 입력된 result를 보여주는 함수 + public func configure(currentCalcText: String) { + self.label.text = currentCalcText + } + + // MARK: - UI Setup + private func setupUI() { + self.backgroundColor = .black + self.addSubview(label) + + setupLabelLayout() + } + + private func setupLabelLayout() { + let labelConstraint = [ + self.label.trailingAnchor.constraint(equalTo: self.layoutMarginsGuide.trailingAnchor), + self.label.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor), + self.label.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor), + ] + NSLayoutConstraint.activate(labelConstraint) + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-missionTests/week6_missionTests.swift b/Week 6/Shimmy/week6-mission/week6-missionTests/week6_missionTests.swift new file mode 100644 index 0000000..2bd298a --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-missionTests/week6_missionTests.swift @@ -0,0 +1,36 @@ +// +// week6_missionTests.swift +// week6-missionTests +// +// Created by Seungbo Shim on 2023/11/07. +// + +import XCTest +@testable import week6_mission + +final class week6_missionTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Week 6/Shimmy/week6-mission/week6-missionUITests/week6_missionUITests.swift b/Week 6/Shimmy/week6-mission/week6-missionUITests/week6_missionUITests.swift new file mode 100644 index 0000000..7da66d8 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-missionUITests/week6_missionUITests.swift @@ -0,0 +1,41 @@ +// +// week6_missionUITests.swift +// week6-missionUITests +// +// Created by Seungbo Shim on 2023/11/07. +// + +import XCTest + +final class week6_missionUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Week 6/Shimmy/week6-mission/week6-missionUITests/week6_missionUITestsLaunchTests.swift b/Week 6/Shimmy/week6-mission/week6-missionUITests/week6_missionUITestsLaunchTests.swift new file mode 100644 index 0000000..f53ca83 --- /dev/null +++ b/Week 6/Shimmy/week6-mission/week6-missionUITests/week6_missionUITestsLaunchTests.swift @@ -0,0 +1,32 @@ +// +// week6_missionUITestsLaunchTests.swift +// week6-missionUITests +// +// Created by Seungbo Shim on 2023/11/07. +// + +import XCTest + +final class week6_missionUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.pbxproj b/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.pbxproj new file mode 100644 index 0000000..799de3f --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.pbxproj @@ -0,0 +1,644 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 03163EC32B0BEC1D00A7236D /* naver_movieApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EC22B0BEC1D00A7236D /* naver_movieApp.swift */; }; + 03163EC52B0BEC1D00A7236D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EC42B0BEC1D00A7236D /* ContentView.swift */; }; + 03163EC72B0BEC1E00A7236D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03163EC62B0BEC1E00A7236D /* Assets.xcassets */; }; + 03163ECA2B0BEC1E00A7236D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03163EC92B0BEC1E00A7236D /* Preview Assets.xcassets */; }; + 03163ED42B0BEC1E00A7236D /* naver_movieTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163ED32B0BEC1E00A7236D /* naver_movieTests.swift */; }; + 03163EDE2B0BEC1E00A7236D /* naver_movieUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EDD2B0BEC1E00A7236D /* naver_movieUITests.swift */; }; + 03163EE02B0BEC1E00A7236D /* naver_movieUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EDF2B0BEC1E00A7236D /* naver_movieUITestsLaunchTests.swift */; }; + 03163EEF2B0BECE400A7236D /* MovieFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EEE2B0BECE400A7236D /* MovieFinder.swift */; }; + 03163EF22B0BEFCE00A7236D /* MovieBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EF12B0BEFCE00A7236D /* MovieBox.swift */; }; + 03163EF52B0BF14C00A7236D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EF42B0BF14C00A7236D /* SearchView.swift */; }; + 03163EF72B0BF15400A7236D /* MovieList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EF62B0BF15400A7236D /* MovieList.swift */; }; + 03163EF92B0BF2A600A7236D /* MovieItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EF82B0BF2A600A7236D /* MovieItem.swift */; }; + 03163EFC2B0BF33600A7236D /* Image+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03163EFB2B0BF33600A7236D /* Image+Extension.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 03163ED02B0BEC1E00A7236D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 03163EB72B0BEC1D00A7236D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 03163EBE2B0BEC1D00A7236D; + remoteInfo = "naver-movie"; + }; + 03163EDA2B0BEC1E00A7236D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 03163EB72B0BEC1D00A7236D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 03163EBE2B0BEC1D00A7236D; + remoteInfo = "naver-movie"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 03163EBF2B0BEC1D00A7236D /* naver-movie.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "naver-movie.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 03163EC22B0BEC1D00A7236D /* naver_movieApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = naver_movieApp.swift; sourceTree = ""; }; + 03163EC42B0BEC1D00A7236D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 03163EC62B0BEC1E00A7236D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 03163EC92B0BEC1E00A7236D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 03163ECF2B0BEC1E00A7236D /* naver-movieTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "naver-movieTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 03163ED32B0BEC1E00A7236D /* naver_movieTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = naver_movieTests.swift; sourceTree = ""; }; + 03163ED92B0BEC1E00A7236D /* naver-movieUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "naver-movieUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 03163EDD2B0BEC1E00A7236D /* naver_movieUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = naver_movieUITests.swift; sourceTree = ""; }; + 03163EDF2B0BEC1E00A7236D /* naver_movieUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = naver_movieUITestsLaunchTests.swift; sourceTree = ""; }; + 03163EEE2B0BECE400A7236D /* MovieFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieFinder.swift; sourceTree = ""; }; + 03163EF12B0BEFCE00A7236D /* MovieBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieBox.swift; sourceTree = ""; }; + 03163EF42B0BF14C00A7236D /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; + 03163EF62B0BF15400A7236D /* MovieList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieList.swift; sourceTree = ""; }; + 03163EF82B0BF2A600A7236D /* MovieItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItem.swift; sourceTree = ""; }; + 03163EFB2B0BF33600A7236D /* Image+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+Extension.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 03163EBC2B0BEC1D00A7236D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03163ECC2B0BEC1E00A7236D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03163ED62B0BEC1E00A7236D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 03163EB62B0BEC1D00A7236D = { + isa = PBXGroup; + children = ( + 03163EC12B0BEC1D00A7236D /* naver-movie */, + 03163ED22B0BEC1E00A7236D /* naver-movieTests */, + 03163EDC2B0BEC1E00A7236D /* naver-movieUITests */, + 03163EC02B0BEC1D00A7236D /* Products */, + ); + sourceTree = ""; + }; + 03163EC02B0BEC1D00A7236D /* Products */ = { + isa = PBXGroup; + children = ( + 03163EBF2B0BEC1D00A7236D /* naver-movie.app */, + 03163ECF2B0BEC1E00A7236D /* naver-movieTests.xctest */, + 03163ED92B0BEC1E00A7236D /* naver-movieUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 03163EC12B0BEC1D00A7236D /* naver-movie */ = { + isa = PBXGroup; + children = ( + 03163EFA2B0BF32100A7236D /* Extensions */, + 03163EF02B0BEFC400A7236D /* Model */, + 03163EED2B0BECD600A7236D /* ViewModel */, + 03163EEC2B0BECA000A7236D /* View */, + 03163EC22B0BEC1D00A7236D /* naver_movieApp.swift */, + 03163EC62B0BEC1E00A7236D /* Assets.xcassets */, + 03163EC82B0BEC1E00A7236D /* Preview Content */, + ); + path = "naver-movie"; + sourceTree = ""; + }; + 03163EC82B0BEC1E00A7236D /* Preview Content */ = { + isa = PBXGroup; + children = ( + 03163EC92B0BEC1E00A7236D /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 03163ED22B0BEC1E00A7236D /* naver-movieTests */ = { + isa = PBXGroup; + children = ( + 03163ED32B0BEC1E00A7236D /* naver_movieTests.swift */, + ); + path = "naver-movieTests"; + sourceTree = ""; + }; + 03163EDC2B0BEC1E00A7236D /* naver-movieUITests */ = { + isa = PBXGroup; + children = ( + 03163EDD2B0BEC1E00A7236D /* naver_movieUITests.swift */, + 03163EDF2B0BEC1E00A7236D /* naver_movieUITestsLaunchTests.swift */, + ); + path = "naver-movieUITests"; + sourceTree = ""; + }; + 03163EEC2B0BECA000A7236D /* View */ = { + isa = PBXGroup; + children = ( + 03163EC42B0BEC1D00A7236D /* ContentView.swift */, + 03163EF42B0BF14C00A7236D /* SearchView.swift */, + 03163EF62B0BF15400A7236D /* MovieList.swift */, + 03163EF82B0BF2A600A7236D /* MovieItem.swift */, + ); + path = View; + sourceTree = ""; + }; + 03163EED2B0BECD600A7236D /* ViewModel */ = { + isa = PBXGroup; + children = ( + 03163EEE2B0BECE400A7236D /* MovieFinder.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 03163EF02B0BEFC400A7236D /* Model */ = { + isa = PBXGroup; + children = ( + 03163EF12B0BEFCE00A7236D /* MovieBox.swift */, + ); + path = Model; + sourceTree = ""; + }; + 03163EFA2B0BF32100A7236D /* Extensions */ = { + isa = PBXGroup; + children = ( + 03163EFB2B0BF33600A7236D /* Image+Extension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 03163EBE2B0BEC1D00A7236D /* naver-movie */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03163EE32B0BEC1E00A7236D /* Build configuration list for PBXNativeTarget "naver-movie" */; + buildPhases = ( + 03163EBB2B0BEC1D00A7236D /* Sources */, + 03163EBC2B0BEC1D00A7236D /* Frameworks */, + 03163EBD2B0BEC1D00A7236D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "naver-movie"; + productName = "naver-movie"; + productReference = 03163EBF2B0BEC1D00A7236D /* naver-movie.app */; + productType = "com.apple.product-type.application"; + }; + 03163ECE2B0BEC1E00A7236D /* naver-movieTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03163EE62B0BEC1E00A7236D /* Build configuration list for PBXNativeTarget "naver-movieTests" */; + buildPhases = ( + 03163ECB2B0BEC1E00A7236D /* Sources */, + 03163ECC2B0BEC1E00A7236D /* Frameworks */, + 03163ECD2B0BEC1E00A7236D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 03163ED12B0BEC1E00A7236D /* PBXTargetDependency */, + ); + name = "naver-movieTests"; + productName = "naver-movieTests"; + productReference = 03163ECF2B0BEC1E00A7236D /* naver-movieTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 03163ED82B0BEC1E00A7236D /* naver-movieUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 03163EE92B0BEC1E00A7236D /* Build configuration list for PBXNativeTarget "naver-movieUITests" */; + buildPhases = ( + 03163ED52B0BEC1E00A7236D /* Sources */, + 03163ED62B0BEC1E00A7236D /* Frameworks */, + 03163ED72B0BEC1E00A7236D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 03163EDB2B0BEC1E00A7236D /* PBXTargetDependency */, + ); + name = "naver-movieUITests"; + productName = "naver-movieUITests"; + productReference = 03163ED92B0BEC1E00A7236D /* naver-movieUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 03163EB72B0BEC1D00A7236D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + TargetAttributes = { + 03163EBE2B0BEC1D00A7236D = { + CreatedOnToolsVersion = 14.3.1; + }; + 03163ECE2B0BEC1E00A7236D = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 03163EBE2B0BEC1D00A7236D; + }; + 03163ED82B0BEC1E00A7236D = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 03163EBE2B0BEC1D00A7236D; + }; + }; + }; + buildConfigurationList = 03163EBA2B0BEC1D00A7236D /* Build configuration list for PBXProject "naver-movie" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 03163EB62B0BEC1D00A7236D; + productRefGroup = 03163EC02B0BEC1D00A7236D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 03163EBE2B0BEC1D00A7236D /* naver-movie */, + 03163ECE2B0BEC1E00A7236D /* naver-movieTests */, + 03163ED82B0BEC1E00A7236D /* naver-movieUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 03163EBD2B0BEC1D00A7236D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03163ECA2B0BEC1E00A7236D /* Preview Assets.xcassets in Resources */, + 03163EC72B0BEC1E00A7236D /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03163ECD2B0BEC1E00A7236D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03163ED72B0BEC1E00A7236D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 03163EBB2B0BEC1D00A7236D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03163EF92B0BF2A600A7236D /* MovieItem.swift in Sources */, + 03163EF72B0BF15400A7236D /* MovieList.swift in Sources */, + 03163EF52B0BF14C00A7236D /* SearchView.swift in Sources */, + 03163EFC2B0BF33600A7236D /* Image+Extension.swift in Sources */, + 03163EC52B0BEC1D00A7236D /* ContentView.swift in Sources */, + 03163EEF2B0BECE400A7236D /* MovieFinder.swift in Sources */, + 03163EF22B0BEFCE00A7236D /* MovieBox.swift in Sources */, + 03163EC32B0BEC1D00A7236D /* naver_movieApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03163ECB2B0BEC1E00A7236D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03163ED42B0BEC1E00A7236D /* naver_movieTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 03163ED52B0BEC1E00A7236D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 03163EE02B0BEC1E00A7236D /* naver_movieUITestsLaunchTests.swift in Sources */, + 03163EDE2B0BEC1E00A7236D /* naver_movieUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 03163ED12B0BEC1E00A7236D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 03163EBE2B0BEC1D00A7236D /* naver-movie */; + targetProxy = 03163ED02B0BEC1E00A7236D /* PBXContainerItemProxy */; + }; + 03163EDB2B0BEC1E00A7236D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 03163EBE2B0BEC1D00A7236D /* naver-movie */; + targetProxy = 03163EDA2B0BEC1E00A7236D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 03163EE12B0BEC1E00A7236D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 03163EE22B0BEC1E00A7236D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 03163EE42B0BEC1E00A7236D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"naver-movie/Preview Content\""; + DEVELOPMENT_TEAM = T5RP2CSNDU; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.naver-movie"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 03163EE52B0BEC1E00A7236D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"naver-movie/Preview Content\""; + DEVELOPMENT_TEAM = T5RP2CSNDU; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.naver-movie"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 03163EE72B0BEC1E00A7236D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.naver-movieTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/naver-movie.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/naver-movie"; + }; + name = Debug; + }; + 03163EE82B0BEC1E00A7236D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.naver-movieTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/naver-movie.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/naver-movie"; + }; + name = Release; + }; + 03163EEA2B0BEC1E00A7236D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.naver-movieUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "naver-movie"; + }; + name = Debug; + }; + 03163EEB2B0BEC1E00A7236D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T5RP2CSNDU; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "shimmy.naver-movieUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "naver-movie"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 03163EBA2B0BEC1D00A7236D /* Build configuration list for PBXProject "naver-movie" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03163EE12B0BEC1E00A7236D /* Debug */, + 03163EE22B0BEC1E00A7236D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 03163EE32B0BEC1E00A7236D /* Build configuration list for PBXNativeTarget "naver-movie" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03163EE42B0BEC1E00A7236D /* Debug */, + 03163EE52B0BEC1E00A7236D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 03163EE62B0BEC1E00A7236D /* Build configuration list for PBXNativeTarget "naver-movieTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03163EE72B0BEC1E00A7236D /* Debug */, + 03163EE82B0BEC1E00A7236D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 03163EE92B0BEC1E00A7236D /* Build configuration list for PBXNativeTarget "naver-movieUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 03163EEA2B0BEC1E00A7236D /* Debug */, + 03163EEB2B0BEC1E00A7236D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 03163EB72B0BEC1D00A7236D /* Project object */; +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcschemes/xcschememanagement.plist b/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..608c223 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie.xcodeproj/xcuserdata/seungboshim.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + naver-movie.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/AccentColor.colorset/Contents.json b/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/AppIcon.appiconset/Contents.json b/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/Contents.json b/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/Extensions/Image+Extension.swift b/Week 7/Shimmy/naver-movie/naver-movie/Extensions/Image+Extension.swift new file mode 100644 index 0000000..855f9f2 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/Extensions/Image+Extension.swift @@ -0,0 +1,23 @@ +// +// Image+Extension.swift +// naver-movie +// +// Created by Seungbo Shim on 2023/11/21. +// + +import SwiftUI + +extension Image { + func ImageModifier() -> some View { + self + .resizable() + .scaledToFit() + } + + func IconModifier() -> some View { + self + .ImageModifier() + .frame(maxWidth: 200) + .foregroundColor(.blue.opacity(0.5)) + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/Model/MovieBox.swift b/Week 7/Shimmy/naver-movie/naver-movie/Model/MovieBox.swift new file mode 100644 index 0000000..8509ded --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/Model/MovieBox.swift @@ -0,0 +1,50 @@ +// +// MovieBox.swift +// naver-movie +// +// Created by Seungbo Shim on 2023/11/21. +// + +import Foundation + +struct MovieBox { + var searchKeyword = "" + var movies: [MovieInfo] = [] +} + +extension MovieBox { + struct MovieInfo: Codable, Identifiable { + let attributedTitle: String? + let link: String + let image: String + let subtitle: String + //let pubDate: Date + let director: String + let actor: String + let userRating: Double + let id: Int + + init(_ movieInfo: MovieFinder.Response.MovieInfo, id: Int) { + attributedTitle = movieInfo.title + link = movieInfo.link + image = movieInfo.image + subtitle = movieInfo.subtitle + //pubDate = movieInfo.pubDate + director = movieInfo.director + actor = movieInfo.actor + userRating = movieInfo.userRating + self.id = id + print(self) + } + } +} + +extension AttributedString { + init?(htmlString: String) { + let option: [NSAttributedString.DocumentReadingOptionKey: NSAttributedString.DocumentType] = [.documentType: .html] + guard let htmlData = htmlString.data(using: .utf16), + let nsStr = try? NSAttributedString(data: htmlData, options: option, documentAttributes: nil) + else { return nil } + self.init(nsStr.string) + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/Preview Content/Preview Assets.xcassets/Contents.json b/Week 7/Shimmy/naver-movie/naver-movie/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/View/ContentView.swift b/Week 7/Shimmy/naver-movie/naver-movie/View/ContentView.swift new file mode 100644 index 0000000..c0da6d1 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/View/ContentView.swift @@ -0,0 +1,35 @@ +// +// ContentView.swift +// naver-movie +// +// Created by Seungbo Shim on 2023/11/21. +// + +import SwiftUI + +struct ContentView: View { + @ObservedObject var viewModel: MovieFinder + + var body: some View { + NavigationView { + ZStack { + ScrollView { + SearchView(viewModel: viewModel) + MovieList(viewModel: viewModel) + } + .foregroundColor(.black) + .navigationTitle("네이버 영화 검색") + if viewModel.fetchingStatus == .fetching { + ProgressView() + .scaleEffect(1.5) + } + } + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView(viewModel: MovieFinder()) + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/View/MovieItem.swift b/Week 7/Shimmy/naver-movie/naver-movie/View/MovieItem.swift new file mode 100644 index 0000000..011509f --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/View/MovieItem.swift @@ -0,0 +1,38 @@ +// +// MovieItem.swift +// naver-movie +// +// Created by Seungbo Shim on 2023/11/21. +// + +import SwiftUI + +struct MovieItem: View { + var movieInfo: MovieBox.MovieInfo + var body: some View { + VStack(alignment: .center) { + Group { + AsyncImage(url: URL(string: movieInfo.image)) { image in + image + .ImageModifier() + } placeholder: { + Image(systemName: "movieclapper") + .IconModifier() + } + .padding(20) + Text(movieInfo.attributedTitle!) + .padding(EdgeInsets(top: 0, leading: 10, bottom: 50, trailing: 10)) + } + } + } +} + +struct MovieItem_Previews: PreviewProvider { + static var movieInfo = MovieBox.MovieInfo(MovieFinder.Response.MovieInfo( + title: "그대들은 어떻게 살 것인가", link: "https://search.naver.com/search.naver?where=nexearch&sm=tab_etc&pkid=68&os=32034616&qvt=0&query=%EA%B7%B8%EB%8C%80%EB%93%A4%EC%9D%80%20%EC%96%B4%EB%96%BB%EA%B2%8C%20%EC%82%B4%20%EA%B2%83%EC%9D%B8%EA%B0%80", image: "https://search.pstatic.net/common?type=o&size=176x264&quality=85&direct=true&src=https%3A%2F%2Fs.pstatic.net%2Fmovie.phinf%2F20231026_9%2F16983044100188P1Ff_JPEG%2Fmovie_image.jpg%3Ftype%3Dw640_2", subtitle: "The Boy and the Heron", director: "미야자키 하야오", actor: "아이묭", userRating: 6.91 + ), id: 1) + + static var previews: some View { + MovieItem(movieInfo: movieInfo) + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/View/MovieList.swift b/Week 7/Shimmy/naver-movie/naver-movie/View/MovieList.swift new file mode 100644 index 0000000..cf8ade2 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/View/MovieList.swift @@ -0,0 +1,26 @@ +// +// MovieList.swift +// naver-movie +// +// Created by Seungbo Shim on 2023/11/21. +// + +import SwiftUI + +struct MovieList: View { + @ObservedObject var viewModel: MovieFinder + var colums: [GridItem] = Array(repeating: .init(.flexible()), count: 2) + + var body: some View { + LazyVGrid(columns: colums) { + ForEach(viewModel.model.movies) { + movieInfo in + NavigationLink { + MovieItem(movieInfo: movieInfo) + } label: { + MovieItem(movieInfo: movieInfo) + } + } + } + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/View/SearchView.swift b/Week 7/Shimmy/naver-movie/naver-movie/View/SearchView.swift new file mode 100644 index 0000000..521e6fb --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/View/SearchView.swift @@ -0,0 +1,33 @@ +// +// SearchView.swift +// naver-movie +// +// Created by Seungbo Shim on 2023/11/21. +// + +import SwiftUI + +struct SearchView: View { + @ObservedObject var viewModel: MovieFinder; + var body: some View { + ZStack { + Color.gray.opacity(0.1) + .padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5)) + HStack { + TextField(text: $viewModel.model.searchKeyword, label: { Text("검색어를 입력하세요.") }) + Button(action: { viewModel.fetchMovieList()}) { + Text("검색") + } + .buttonStyle(.bordered) + } + .padding() + } + } +} + +struct SearchView_Previews: PreviewProvider { + static var previews: some View { + SearchView(viewModel: MovieFinder()) + } +} + diff --git a/Week 7/Shimmy/naver-movie/naver-movie/ViewModel/MovieFinder.swift b/Week 7/Shimmy/naver-movie/naver-movie/ViewModel/MovieFinder.swift new file mode 100644 index 0000000..07148f4 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/ViewModel/MovieFinder.swift @@ -0,0 +1,102 @@ +// +// MovieFinder.swift +// naver-movie +// +// Created by Seungbo Shim on 2023/11/21. +// + +import SwiftUI + +fileprivate enum NaverOpenAPI { + static let clientID = "VqtHIaKV56Yq0bbtzKXe" + static let clientSecret = "3CDDCRzxdH" + + static let scheme = "https" + static let host = "openapi.naver.com" + + enum Path: String { + case movie = "/v1/search/movie.json" + } +} + +class MovieFinder: ObservableObject { + @Published var model = MovieBox() + @Published var fetchingStatus = FetchStatus.idle + + func fetchMovieList() { + fetchingStatus = .fetching + + // urlComponents + var urlComponents = URLComponents() + urlComponents.scheme = NaverOpenAPI.scheme + urlComponents.host = NaverOpenAPI.host + urlComponents.path = NaverOpenAPI.Path.movie.rawValue + urlComponents.queryItems = [URLQueryItem(name: "query", value: model.searchKeyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed))] + + // 검색 쿼리를 UTF-8로 인코딩하여 설정 +// if let encodedQuery = model.searchKeyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { +// urlComponents.queryItems = [URLQueryItem(name: "query", value: encodedQuery)] +// } + + // urlComponents -> url + guard let url = urlComponents.url else { return } + + // request (header) + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = "GET" // method + urlRequest.addValue(NaverOpenAPI.clientID, forHTTPHeaderField: "X-Naver-Client-Id") // value, key + urlRequest.addValue(NaverOpenAPI.clientSecret, forHTTPHeaderField: "X-Naver-Client-Secret") + + // 비동기로 수행할 작업: task + let task = URLSession.shared.dataTask(with: urlRequest) {data, response, error in + // data, response, error 받아와서 처리할 부분 + guard error == nil, + let httpURLResponse = response as?HTTPURLResponse, + (200 ... 299).contains(httpURLResponse.statusCode), + let data = data, + let parsedData = try? JSONDecoder().decode(Response.self, from: data) + // 받아온 data(101011.....)를 우리가 볼수있게 parsing + else { return } + + // 비동기 함수를 main thread 에서 실행 + // mainthread에서 값이 바뀌어야 multithread 에서 갱신가능 + DispatchQueue.main.async { [weak self] in + // 서로 참조할 때, 다른 한 쪽에서 참조를 끊으면 자동으로 할당 해제 + // weak self 에서는 self? 사용 + // 받아온 JSON을 image로 parsing하고 ImageBox.ImageInfo에 넣어줌 + self?.model.movies = parsedData.items.indices.map { + MovieBox.MovieInfo(parsedData.items[$0], id: parsedData.start + $0) + } + // fetchingStatus를 idle로 + self?.fetchingStatus = .idle + } + } + task.resume() // mainthread로 넘길래 + } +} + +extension MovieFinder { + struct Response: Codable { + let lastBuildDate: String + let total: Int + let start: Int + let display: Int + let items: [MovieInfo] + + struct MovieInfo: Codable { + let title: String + let link: String + let image: String + let subtitle: String + //let pubDate: Date + let director: String + let actor: String + let userRating: Double + } + } + + enum FetchStatus { + case idle + case fetching + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movie/naver_movieApp.swift b/Week 7/Shimmy/naver-movie/naver-movie/naver_movieApp.swift new file mode 100644 index 0000000..9a12f6b --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movie/naver_movieApp.swift @@ -0,0 +1,17 @@ +// +// naver_movieApp.swift +// naver-movie +// +// Created by Seungbo Shim on 2023/11/21. +// + +import SwiftUI + +@main +struct naver_movieApp: App { + var body: some Scene { + WindowGroup { + ContentView(viewModel: MovieFinder()) + } + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movieTests/naver_movieTests.swift b/Week 7/Shimmy/naver-movie/naver-movieTests/naver_movieTests.swift new file mode 100644 index 0000000..0f37284 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movieTests/naver_movieTests.swift @@ -0,0 +1,36 @@ +// +// naver_movieTests.swift +// naver-movieTests +// +// Created by Seungbo Shim on 2023/11/21. +// + +import XCTest +@testable import naver_movie + +final class naver_movieTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Week 7/Shimmy/naver-movie/naver-movieUITests/naver_movieUITests.swift b/Week 7/Shimmy/naver-movie/naver-movieUITests/naver_movieUITests.swift new file mode 100644 index 0000000..2f2d4b7 --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movieUITests/naver_movieUITests.swift @@ -0,0 +1,41 @@ +// +// naver_movieUITests.swift +// naver-movieUITests +// +// Created by Seungbo Shim on 2023/11/21. +// + +import XCTest + +final class naver_movieUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/Week 7/Shimmy/naver-movie/naver-movieUITests/naver_movieUITestsLaunchTests.swift b/Week 7/Shimmy/naver-movie/naver-movieUITests/naver_movieUITestsLaunchTests.swift new file mode 100644 index 0000000..061c07c --- /dev/null +++ b/Week 7/Shimmy/naver-movie/naver-movieUITests/naver_movieUITestsLaunchTests.swift @@ -0,0 +1,32 @@ +// +// naver_movieUITestsLaunchTests.swift +// naver-movieUITests +// +// Created by Seungbo Shim on 2023/11/21. +// + +import XCTest + +final class naver_movieUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +}