diff --git a/AppleSnack.xcodeproj/project.pbxproj b/AppleSnack.xcodeproj/project.pbxproj index 16272e0..6ec99a7 100644 --- a/AppleSnack.xcodeproj/project.pbxproj +++ b/AppleSnack.xcodeproj/project.pbxproj @@ -13,6 +13,10 @@ 520F91FE2A8A407E005C8897 /* MySnack+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520F91FA2A8A407E005C8897 /* MySnack+CoreDataProperties.swift */; }; 520F92022A8A42A7005C8897 /* SnackCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520F92012A8A42A7005C8897 /* SnackCore.swift */; }; 520F92042A8A42B3005C8897 /* ProfileCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520F92032A8A42B3005C8897 /* ProfileCore.swift */; }; + 632D14132A8CA26E0008BB97 /* WriteViewStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 632D14122A8CA26E0008BB97 /* WriteViewStoryboard.storyboard */; }; + 632D14152A8CA46C0008BB97 /* WriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 632D14142A8CA46C0008BB97 /* WriteViewController.swift */; }; + 63441B942A8A367D0017994F /* DetailViewStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63441B932A8A367D0017994F /* DetailViewStoryboard.storyboard */; }; + 63441B962A8A3C2C0017994F /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63441B952A8A3C2C0017994F /* DetailViewController.swift */; }; 634976302A8A13D500A071CA /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 6349762F2A8A13D500A071CA /* .gitignore */; }; 63523EC52A8A0EE7002C677A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63523EC42A8A0EE7002C677A /* AppDelegate.swift */; }; 63523EC72A8A0EE7002C677A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63523EC62A8A0EE7002C677A /* SceneDelegate.swift */; }; @@ -21,6 +25,10 @@ 63523ECF2A8A0EE7002C677A /* AppleSnack.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 63523ECD2A8A0EE7002C677A /* AppleSnack.xcdatamodeld */; }; 63523ED12A8A0EE8002C677A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63523ED02A8A0EE8002C677A /* Assets.xcassets */; }; 63523ED42A8A0EE8002C677A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63523ED22A8A0EE8002C677A /* LaunchScreen.storyboard */; }; + D14C85712A8BC79600A9216D /* Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14C85702A8BC79600A9216D /* Help.swift */; }; + D1AB9A402A8A3D9A00EB4C56 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1AB9A3F2A8A3D9A00EB4C56 /* ProfileViewController.swift */; }; + D1AB9A422A8A3EAC00EB4C56 /* ProfileView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D1AB9A412A8A3EAC00EB4C56 /* ProfileView.storyboard */; }; + E50EA25E2A8DF1EA00F8FA13 /* MainCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E50EA25D2A8DF1EA00F8FA13 /* MainCollectionViewCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -30,6 +38,10 @@ 520F91FA2A8A407E005C8897 /* MySnack+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MySnack+CoreDataProperties.swift"; sourceTree = ""; }; 520F92012A8A42A7005C8897 /* SnackCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnackCore.swift; sourceTree = ""; }; 520F92032A8A42B3005C8897 /* ProfileCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCore.swift; sourceTree = ""; }; + 632D14122A8CA26E0008BB97 /* WriteViewStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WriteViewStoryboard.storyboard; sourceTree = ""; }; + 632D14142A8CA46C0008BB97 /* WriteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteViewController.swift; sourceTree = ""; }; + 63441B932A8A367D0017994F /* DetailViewStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = DetailViewStoryboard.storyboard; sourceTree = ""; }; + 63441B952A8A3C2C0017994F /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 6349762F2A8A13D500A071CA /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 63523EC12A8A0EE7002C677A /* AppleSnack.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppleSnack.app; sourceTree = BUILT_PRODUCTS_DIR; }; 63523EC42A8A0EE7002C677A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -40,6 +52,10 @@ 63523ED02A8A0EE8002C677A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 63523ED32A8A0EE8002C677A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63523ED52A8A0EE8002C677A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D14C85702A8BC79600A9216D /* Help.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Help.swift; sourceTree = ""; }; + D1AB9A3F2A8A3D9A00EB4C56 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; + D1AB9A412A8A3EAC00EB4C56 /* ProfileView.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProfileView.storyboard; sourceTree = ""; }; + E50EA25D2A8DF1EA00F8FA13 /* MainCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCollectionViewCell.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -93,12 +109,20 @@ 63523EC32A8A0EE7002C677A /* AppleSnack */ = { isa = PBXGroup; children = ( - 520F92002A8A4288005C8897 /* Model(profile) */, - 520F91FF2A8A408A005C8897 /* Model(snack) */, 63523EC42A8A0EE7002C677A /* AppDelegate.swift */, 63523EC62A8A0EE7002C677A /* SceneDelegate.swift */, - 63523EC82A8A0EE7002C677A /* ViewController.swift */, + 520F92002A8A4288005C8897 /* Model(profile) */, + 520F91FF2A8A408A005C8897 /* Model(snack) */, 63523ECA2A8A0EE7002C677A /* Main.storyboard */, + E50EA25D2A8DF1EA00F8FA13 /* MainCollectionViewCell.swift */, + 63441B932A8A367D0017994F /* DetailViewStoryboard.storyboard */, + 632D14122A8CA26E0008BB97 /* WriteViewStoryboard.storyboard */, + D1AB9A412A8A3EAC00EB4C56 /* ProfileView.storyboard */, + 632D14142A8CA46C0008BB97 /* WriteViewController.swift */, + 63523EC82A8A0EE7002C677A /* ViewController.swift */, + 63441B952A8A3C2C0017994F /* DetailViewController.swift */, + D1AB9A3F2A8A3D9A00EB4C56 /* ProfileViewController.swift */, + D14C85702A8BC79600A9216D /* Help.swift */, 63523ED02A8A0EE8002C677A /* Assets.xcassets */, 63523ED22A8A0EE8002C677A /* LaunchScreen.storyboard */, 63523ED52A8A0EE8002C677A /* Info.plist */, @@ -167,7 +191,10 @@ files = ( 634976302A8A13D500A071CA /* .gitignore in Resources */, 63523ED42A8A0EE8002C677A /* LaunchScreen.storyboard in Resources */, + 63441B942A8A367D0017994F /* DetailViewStoryboard.storyboard in Resources */, + 632D14132A8CA26E0008BB97 /* WriteViewStoryboard.storyboard in Resources */, 63523ED12A8A0EE8002C677A /* Assets.xcassets in Resources */, + D1AB9A422A8A3EAC00EB4C56 /* ProfileView.storyboard in Resources */, 63523ECC2A8A0EE7002C677A /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -180,8 +207,13 @@ buildActionMask = 2147483647; files = ( 520F91FE2A8A407E005C8897 /* MySnack+CoreDataProperties.swift in Sources */, + 63441B962A8A3C2C0017994F /* DetailViewController.swift in Sources */, + D1AB9A402A8A3D9A00EB4C56 /* ProfileViewController.swift in Sources */, + E50EA25E2A8DF1EA00F8FA13 /* MainCollectionViewCell.swift in Sources */, 63523EC92A8A0EE7002C677A /* ViewController.swift in Sources */, 520F92022A8A42A7005C8897 /* SnackCore.swift in Sources */, + D14C85712A8BC79600A9216D /* Help.swift in Sources */, + 632D14152A8CA46C0008BB97 /* WriteViewController.swift in Sources */, 63523ECF2A8A0EE7002C677A /* AppleSnack.xcdatamodeld in Sources */, 63523EC52A8A0EE7002C677A /* AppDelegate.swift in Sources */, 63523EC72A8A0EE7002C677A /* SceneDelegate.swift in Sources */, diff --git a/AppleSnack.xcodeproj/project.xcworkspace/xcuserdata/sseongwon.xcuserdatad/UserInterfaceState.xcuserstate b/AppleSnack.xcodeproj/project.xcworkspace/xcuserdata/sseongwon.xcuserdatad/UserInterfaceState.xcuserstate index 5876853..2c5f592 100644 Binary files a/AppleSnack.xcodeproj/project.xcworkspace/xcuserdata/sseongwon.xcuserdatad/UserInterfaceState.xcuserstate and b/AppleSnack.xcodeproj/project.xcworkspace/xcuserdata/sseongwon.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/AppleSnack/Assets.xcassets/cat.imageset/Contents.json b/AppleSnack/Assets.xcassets/cat.imageset/Contents.json new file mode 100644 index 0000000..3866ee0 --- /dev/null +++ b/AppleSnack/Assets.xcassets/cat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "cat.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AppleSnack/Assets.xcassets/cat.imageset/cat.png b/AppleSnack/Assets.xcassets/cat.imageset/cat.png new file mode 100644 index 0000000..0415541 Binary files /dev/null and b/AppleSnack/Assets.xcassets/cat.imageset/cat.png differ diff --git a/AppleSnack/Base.lproj/Main.storyboard b/AppleSnack/Base.lproj/Main.storyboard index 25a7638..8ab232f 100644 --- a/AppleSnack/Base.lproj/Main.storyboard +++ b/AppleSnack/Base.lproj/Main.storyboard @@ -1,24 +1,162 @@ - + + - + + + + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppleSnack/DetailViewController.swift b/AppleSnack/DetailViewController.swift new file mode 100644 index 0000000..9d7fad2 --- /dev/null +++ b/AppleSnack/DetailViewController.swift @@ -0,0 +1,97 @@ +// +// DetailPageViewController.swift +// AppleSnack +// +// Created by 박성원 on 2023/08/14. +// + +import UIKit + +final class DetailViewController: UIViewController { + + + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var contextLabel: UILabel! + @IBOutlet weak var urlContextLabel: UILabel! + + var mySnack: MySnack? + + // 데이터 변수 -> 나오는 걸 보기 위해 선언 + var mainTitle: String? + var content: String? + var url: String? + var category: String? + + + override func viewDidLoad() { + super.viewDidLoad() + setupNaviBar() + configureUI() + contextLabel.textAlignment = .justified + } + + // MARK: - configureUI 세팅 + + private func configureUI() { + + titleLabel.text = mainTitle + contextLabel.text = content + urlContextLabel.text = url +// if let mySnack = self.mySnack { +// +// guard let text = mySnack.title, let context = mySnack.text, let url = mySnack.assiURL else { return } +// +// titleLabel.text = text +// contextLabel.text = context +// urlContextLabel.text = url +// } + } + + // MARK: - 네비게이션 바 설정 + + private func setupNaviBar() { + + self.title = "상세 페이지" + self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.systemOrange] + + + + let doneButton = UIBarButtonItem(title: "수정", style: .done, target: self, action: #selector(doneButtonTapped)) + + doneButton.tintColor = .orange + navigationItem.rightBarButtonItem = doneButton + + let backButton = UIBarButtonItem(title: "뒤로", style: .done, target: self, action: #selector(backButtonTapped)) + + backButton.tintColor = .orange + navigationItem.leftBarButtonItem = backButton + } + + @objc func doneButtonTapped() { + + //수정 페이지 보냄 + let storyboard = UIStoryboard(name: "WriteViewStoryboard", bundle: nil) + let vc = storyboard.instantiateViewController(withIdentifier: "WriteViewStoryboard") as! WriteViewController + + vc.mainTitle = titleLabel.text + vc.content = contextLabel.text + vc.url = urlContextLabel.text + //vc.category = categorie + + + self.navigationController?.pushViewController(vc, animated: true) + } + + @objc func backButtonTapped() { + //백하면 바로 셀있는 뷰로 이동 +// guard let viewControllerStack = self.navigationController?.viewControllers else { return } +// for viewController in viewControllerStack { +// if let _ = viewController as? _ { +// self.navigationController?.popToViewController(_, animated: true) +// } +// } + } + + +} + diff --git a/AppleSnack/DetailViewStoryboard.storyboard b/AppleSnack/DetailViewStoryboard.storyboard new file mode 100644 index 0000000..1bdaf67 --- /dev/null +++ b/AppleSnack/DetailViewStoryboard.storyboard @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppleSnack/Help.swift b/AppleSnack/Help.swift new file mode 100644 index 0000000..7d338a0 --- /dev/null +++ b/AppleSnack/Help.swift @@ -0,0 +1,41 @@ +// +// Help.swift +// AppleSnack +// +// Created by 조규연 on 2023/08/15. +// + +import UIKit + +extension ProfileViewController { + func makeImageLayer() { + profileImage.layer.cornerRadius = profileImage.frame.size.width / 2 + profileImage.layer.masksToBounds = true // imageView 안으로 제한 + profileImage.layer.borderWidth = 2.0 + profileImage.layer.borderColor = UIColor.black.cgColor + } + + func makeTextViewLayer() { + blogTextView.layer.borderWidth = 1.0 + blogTextView.layer.borderColor = UIColor.gray.cgColor + blogTextView.layer.cornerRadius = 10 + + githubTextView.layer.borderWidth = 1.0 + githubTextView.layer.borderColor = UIColor.gray.cgColor + githubTextView.layer.cornerRadius = 10 + } +} + +extension WriteViewController { + + +// func makeTextViewLayer() { +// blogTextView.layer.borderWidth = 1.0 +// blogTextView.layer.borderColor = UIColor.gray.cgColor +// blogTextView.layer.cornerRadius = 10 +// +// githubTextView.layer.borderWidth = 1.0 +// githubTextView.layer.borderColor = UIColor.gray.cgColor +// githubTextView.layer.cornerRadius = 10 +// } +} diff --git a/AppleSnack/MainCollectionViewCell.swift b/AppleSnack/MainCollectionViewCell.swift new file mode 100644 index 0000000..8427522 --- /dev/null +++ b/AppleSnack/MainCollectionViewCell.swift @@ -0,0 +1,26 @@ +// +// MainCollectionViewCell.swift +// AppleSnack +// +// Created by on 2023/08/17. +// + +import UIKit + +class Cell: UICollectionViewCell { + @IBOutlet var newCell: UILabel! + @IBOutlet weak var deleteButton: UIButton! + + let label: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.numberOfLines = 0 // Allow multiple lines + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + func configure(text: String) { + label.text = text + } + +} diff --git a/AppleSnack/Model(profile)/SnackCore.swift b/AppleSnack/Model(profile)/SnackCore.swift index a121b6f..9b4fde8 100644 --- a/AppleSnack/Model(profile)/SnackCore.swift +++ b/AppleSnack/Model(profile)/SnackCore.swift @@ -28,7 +28,7 @@ final class SnackManager { snackData.categorie = categorie snackData.assiURL = assiUrl snackData.date = Date() - + print("저장 완료") appDelegate?.saveContext() } } @@ -71,6 +71,7 @@ final class SnackManager { if var targetSnack = fetchedSnack.first { targetSnack = newSnackData appDelegate?.saveContext() + print("업데이트 완료") } completion() } catch { diff --git a/AppleSnack/Model(snack)/ProfileCore.swift b/AppleSnack/Model(snack)/ProfileCore.swift index a25219f..0536bfc 100644 --- a/AppleSnack/Model(snack)/ProfileCore.swift +++ b/AppleSnack/Model(snack)/ProfileCore.swift @@ -1,7 +1,7 @@ import UIKit import CoreData -//MARK: - To do 관리하는 매니저 (코어데이터 관리) +//MARK: - To do 관리하는 매니저 (코어데이터 관리) / final class ProfileManager { diff --git a/AppleSnack/ProfileView.storyboard b/AppleSnack/ProfileView.storyboard new file mode 100644 index 0000000..33128cb --- /dev/null +++ b/AppleSnack/ProfileView.storyboard @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppleSnack/ProfileViewController.swift b/AppleSnack/ProfileViewController.swift new file mode 100644 index 0000000..9a6828a --- /dev/null +++ b/AppleSnack/ProfileViewController.swift @@ -0,0 +1,140 @@ +// +// File.swift +// AppleSnack +// +// Created by 조규연 on 2023/08/14. +// + +import UIKit + +class ProfileViewController:UIViewController, UIImagePickerControllerDelegate, UITextViewDelegate, UINavigationControllerDelegate { + let profileManager = ProfileManager.shared + override func viewDidLoad() { + super.viewDidLoad() + + if let profileData = profileManager.getToDoListFromCoreData().first { + nameField.text = profileData.name + githubTextView.text = profileData.github + blogTextView.text = profileData.blog + + if let imageDate = profileData.photo, let image = UIImage(data: imageDate) { + profileImage.image = image + } + } + + makeImageLayer() + makeTextViewLayer() + updateProgressView() + + blogTextView.delegate = self + githubTextView.delegate = self + // 옵저버를 추가해서 업데이트 요청을 받음 + NotificationCenter.default.addObserver(self, selector: #selector(requestProgressUpdate), name: NSNotification.Name( "RequestProgressUPdate"), object: nil) + } + + @objc func requestProgressUpdate() { + updateProgressView() + } + + @IBAction func imageTapped(_ sender: UITapGestureRecognizer) { + let imagePicker = UIImagePickerController() + imagePicker.sourceType = .photoLibrary + imagePicker.delegate = self + present(imagePicker, animated: true, completion: nil) + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + if let selectedImage = info[.originalImage] as? UIImage { + profileImage.image = selectedImage + + profileManager.saveToDoData(name: nameField.text, photo: selectedImage.pngData(), git: githubTextView.text, blog: blogTextView.text) {} + } + picker.dismiss(animated: true, completion: nil) + } + + @IBAction func editButtonTapped(_ sender: UIBarButtonItem) { + toggleFieldState(nameField, sender: sender) + toggleFieldState(emailField, sender: sender) + toggleTextViewState(blogTextView, sender: sender) + toggleTextViewState(githubTextView, sender: sender) + profileImage.isUserInteractionEnabled.toggle() + + if let profileData = profileManager.getToDoListFromCoreData().first { + profileData.name = nameField.text + profileData.github = githubTextView.text + profileData.blog = blogTextView.text + + if let newImage = profileImage.image { + profileData.photo = newImage.pngData() + } + + profileManager.updateToDo(newToDoData: profileData) {} + } + } + + @IBOutlet weak var profileImage: UIImageView! + + @IBOutlet weak var nameField: UITextField! + + @IBOutlet weak var emailField: UITextField! + + @IBOutlet weak var blogTextView: UITextView! + + @IBOutlet weak var githubTextView: UITextView! + + + @IBOutlet weak var progressView: UIProgressView! + + @IBOutlet weak var currentValueLabel: UILabel! + + @IBOutlet weak var levelLabel: UILabel! + + @IBOutlet weak var currentValueLabelLeadingConstraint: NSLayoutConstraint! + + var dataArray: [Any] = [1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2] // 임시 + var currentValue: Float = 0.0 + var level: Int = 0 + var maxValue: Float = 100 + + func updateProgressView() { + currentValue = Float(dataArray.count) + let repeatedValue = currentValue.truncatingRemainder(dividingBy: maxValue) + if repeatedValue == 0 && currentValue != 0 { + level += 1 + levelLabel.text = "Lv: \(level)" + } + + progressView.progress = repeatedValue / maxValue + currentValueLabel.text = "\(Int(repeatedValue))℃" + let ratio = CGFloat(repeatedValue / maxValue) + + currentValueLabelLeadingConstraint.constant = progressView.bounds.width * ratio + } + + func toggleFieldState(_ field: UITextField, sender: UIBarButtonItem) { + field.isEnabled.toggle() + + if field.isEnabled { + sender.title = "완료" + } else { + sender.title = "수정" + } + } + + func toggleTextViewState(_ textView: UITextView, sender: UIBarButtonItem) { + textView.isEditable.toggle() + + if textView.isEditable { + sender.title = "완료" + } else { + sender.title = "수정" + } + } + + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { + if UIApplication.shared.canOpenURL(URL) { + UIApplication.shared.open(URL, options: [:], completionHandler: nil) + } + return false + } +} diff --git a/AppleSnack/ViewController.swift b/AppleSnack/ViewController.swift index ac756ff..c50fac5 100644 --- a/AppleSnack/ViewController.swift +++ b/AppleSnack/ViewController.swift @@ -5,15 +5,247 @@ // Created by 박성원 on 2023/08/14. // -import UIKit - -class ViewController: UIViewController { +import UIKit // Foundation 프레임워크를 내부적으로 import하고 있음 +class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{ + + var data: [String] = ["클래스", "구조체"] + var selectedIndexPaths: Set = [] // collection cell를 선택해서 삭제하기 위해서 필요 + + @IBOutlet weak var myCollectionView: UICollectionView! + @IBOutlet weak var newButton: UIButton! + @IBOutlet weak var floatingStackView: UIStackView! + @IBOutlet weak var floatingButton: UIButton! + @IBOutlet weak var fixButton: UIButton! + @IBOutlet weak var deleteButton: UIButton! + + lazy var floatingDimView: UIView = { + let view = UIView(frame: self.view.frame) + view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0) + view.alpha = 0 + view.isHidden = true + + self.view.insertSubview(view, belowSubview: self.floatingStackView) + + return view + + }() + + var isShowFloating: Bool = false + + lazy var buttons: [UIButton] = [self.fixButton, self.deleteButton, self.newButton] + + + + // MARK: - viewDidLoad + override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. + + // MARK: - Lifecycles + + // Layout 간격 설정 + + let flowLayout = UICollectionViewFlowLayout() + + + if let flowLayout = myCollectionView?.collectionViewLayout as? UICollectionViewFlowLayout { + flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + } + + // flowLayout = UICollectionView(frame: view.bounds, collectionViewLayout: flowLayout) + flowLayout.register(Cell.self, forDecorationViewOfKind: "Cell") + flowLayout.minimumLineSpacing = 0.5 // 셀의 세로 간격 + flowLayout.minimumInteritemSpacing = 0.5 // 셀의 가로 간격 + + + flowLayout.itemSize = CGSize(width: 100, height: 50) + myCollectionView.collectionViewLayout = flowLayout // 기본 세팅 + + + // 콜렉션 뷰에 대한 설정 + myCollectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + myCollectionView.dataSource = self + myCollectionView.delegate = self + + + // let viewModel = CommentViewModel(comment: comments[IndexPath.row]) + // let height = viewModel.size(forWidth: view.frame.width).height + // return CGSize(width: view.frame.width, height: height) + } + + + + // MARK: - FloationgButton + + @IBAction func floatingButtonAction(_ sender: UIButton) { + + if isShowFloating { + buttons.reversed().forEach { button in + UIView.animate(withDuration: 0.3){ + button.isHidden = true // "편집" 버튼을 눌렀을 때 다시 접히게 해줌 + self.view.layoutIfNeeded() + + } + } + + UIView.animate(withDuration: 0.5, animations: { + self.floatingDimView.alpha = 0 + }) { (_) in + self.floatingDimView.isHidden = true + } + } else { + self.floatingDimView.isHidden = false + + UIView.animate(withDuration: 0.5) { + self.floatingDimView.alpha = 1 + } + + buttons.forEach { [weak self] button in + button.isHidden = false + button.alpha = 0 + + UIView.animate(withDuration: 0.5) { + button.alpha = 1 + self?.view.layoutIfNeeded() + + } + } + } + + isShowFloating = !isShowFloating + + let image = isShowFloating ? UIImage(named: "Hide") : UIImage(named: "Show") + let roatation = isShowFloating ? CGAffineTransform(rotationAngle: .pi - (.pi / 1)) : CGAffineTransform.identity + + UIView.animate(withDuration: 0.3) { + sender.setImage(image, for: .normal) + sender.transform = roatation + + } } + + // MARK: - UICollectionViewDataSource + + + // 지정된 섹션에 표시할 셀의 개수를 묻는 메서드 + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return data.count // 내가 표시할 컬렉션뷰의 개수 + } + + // 각 컬렉션뷰 셀에 대한 설정 or 컬렉션 뷰의 지정된 위치에 표시할 셀을 요청하는 메서드 + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + // 다운케스팅 - 셀의 인스턴스 + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell + + // 데이터에 따른 UI 변경 + // 라벨 설정 + // cell.Cell.text = self.data[indexPath.item] + // cell.label.addTarget(self, action: #selector(addCellButtonTapped(_ :)), for: .touchUpInside) + + // 배경화면 + cell.contentView.backgroundColor = UIColor.systemBlue + cell.contentView.layer.cornerRadius = 25 + cell.contentView.layer.borderWidth = 1 + cell.newCell.text = data[indexPath.row] // cell에 입력한 label이 나오게 해줌 + + cell.configure(text: data[indexPath.item]) + cell.deleteButton.tag = indexPath.item + cell.deleteButton.addTarget(self, action: #selector(deletButton(_ :)), for: .touchUpInside) + + return cell + } + + @objc func addCellButtonTapped(_ sender: UIButton) { + let indexPath = IndexPath(item: data.count, section: 0) + data.append("Cell \(data.count + 1)") + myCollectionView.insertItems(at: [indexPath]) + } + // return UICollectionViewCell() // 내가 표시하고자하는 셀 + + // MARK: - UICollectionViewDelegateFlowLayout + + private func myCollectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) -> CGSize { + + let width: CGFloat = (collectionView.frame.width / 3) - 0.5 + + return CGSize(width: width, height: width) + + if let cell = myCollectionView.cellForItem(at: indexPath) { + cell.contentView.backgroundColor = .green + print("작동중") + + // 셀을 선택했을 때 호출해서 페이지 전환 + // switch indexPath.item { + // // 첫 번째 셀 선택 시, 다른 페이지로 이동하는 동작 + // + // case 0: + // let nextpageController = NextPageViewController() // 이동할 뷰 컨트롤러 인스턴스 생성 + // navigationController?.pushViewController(nextpageController, animated: true) + // + // case 1: + // // 두 번째 셀 선택 시, 다른 페이지로 이동하는 동작을 구현 + // + // default: + + // break + // } + } + } + func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + // 선택 해제된 셀의 배경색을 원래대로 되돌립니다. + if let cell = collectionView.cellForItem(at: indexPath) { + cell.contentView.backgroundColor = .systemBlue + } + } + + // cell 크기 조정 + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + +// let text = data[indexPath.item] +// let width = collectionView.bounds.width - 20 + // let height = text.height(withConstrainedWidth: width, font: UIFont.systemFont(ofSize: 10)) + + return CGSize(width: 100, height: 50) + } + // MARK: - alert Button Action + + @IBAction func newButton(_ sender: UIButton) { + + let alertController = UIAlertController(title: "카테고리를 생성합니다.", message: "", preferredStyle: .alert) + + alertController.addTextField { textField in + textField.placeholder = "입력하세요" + } + + let addAction = UIAlertAction(title: "생성", style: .default) { [weak self, weak alertController] _ in + if let textField = alertController?.textFields?.first, let text = textField.text, !text.isEmpty { + self?.data.append(text) + self?.myCollectionView.reloadData() + + } + + } + + let cancelAction = UIAlertAction(title: "취소", style: .cancel) + + alertController.addAction(addAction) + alertController.addAction(cancelAction) + + present(alertController, animated: true, completion: nil) + + + } + + + + // MARK: - deletButton -} - + + @IBAction func deletButton(_ sender: UIButton) { + + } + + } diff --git a/AppleSnack/WriteViewController.swift b/AppleSnack/WriteViewController.swift new file mode 100644 index 0000000..998932a --- /dev/null +++ b/AppleSnack/WriteViewController.swift @@ -0,0 +1,292 @@ +// +// WriteViewController.swift +// AppleSnack +// +// Created by 박성원 on 2023/08/16. +// + +// 1. 텍스트 필드를 다 쓰소 다음을 누르면 아래로 커서 이동 (o) +// 2. 키보드 높이만큼 화면도 올라가기 (o) +// 3. titleTextField(텍스트필드)가 비어있으면 저장 못하게 하기 -> alert창으로 알려주기 (o) +// 5. 가져와서 +// 6. -> create +// 7. -> update +// 8. + + +import UIKit + + + +final class WriteViewController: UIViewController { + + + + @IBOutlet weak var titleTextField: UITextField! + + @IBOutlet weak var contextTextView: UITextView! + + @IBOutlet weak var urlTextView: UITextView! + + + var mySnack: MySnack? + + var snackManager = SnackManager.shared + + + var mainTitle: String? + var content: String? + var url: String? + var category: String? + + + override func viewDidLoad() { + super.viewDidLoad() + setupNaviBar() + configureUI() + setup() + setupKeyboardEvent() + + } + + + // MARK: - setup() 세팅 (테두리) + + + private func setup() { + contextTextView.delegate = self + urlTextView.delegate = self + titleTextField.delegate = self + + contextTextView.layer.borderWidth = 1.0 + contextTextView.layer.borderColor = UIColor.lightGray.cgColor + contextTextView.layer.cornerRadius = 12 + + urlTextView.layer.borderWidth = 1.0 + urlTextView.layer.borderColor = UIColor.lightGray.cgColor + urlTextView.layer.cornerRadius = 12 + + titleTextField.layer.borderWidth = 1.0 + titleTextField.layer.cornerRadius = 12 + titleTextField.layer.borderColor = UIColor.lightGray.cgColor + titleTextField.clearButtonMode = .always //오른쪽에 'x' 버튼 + titleTextField.returnKeyType = .next //리턴 버튼 수정 + + } + + // MARK: - configureUI() (bar title , placeholder setting) + + private func configureUI() { + snackManager.getToDoListFromCoreData() + // 기존에 데이터가 있을때 + if let mySnack = self.mySnack { + self.title = "수정 페이지" + + titleTextField.text = mySnack.title + contextTextView.text = mySnack.text + urlTextView.text = mySnack.assiURL + // category + + // 기존데이터가 없을때 + } else { + self.title = "생성 페이지" + + contextTextView.text = "내용을 입력하세요." + contextTextView.textColor = .lightGray + + urlTextView.text = "도움이 될 만한 주소를 입력하세요." + urlTextView.textColor = .lightGray + } + } + + + + // MARK: - 네비게이션 바 설정 + + private func setupNaviBar() { + + self.title = "글 추가" + self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.systemOrange] + + + + let doneButton = UIBarButtonItem(title: "완료", style: .done, target: self, action: #selector(doneButtonTapped)) + + doneButton.tintColor = .orange + navigationItem.rightBarButtonItem = doneButton + + let backButton = UIBarButtonItem(title: "뒤로", style: .done, target: self, action: #selector(backButtonTapped)) + + backButton.tintColor = .orange + navigationItem.leftBarButtonItem = backButton + } + + // MARK: - 완료버튼 누르면 이벤트 함수 + + @objc func doneButtonTapped() { + + let titleTextField = titleTextField.text + + //만약 타이틀이 빈값이면 + if (titleTextField!.isEmpty) { + titleIsEmptyAlertMessage() + return + } else { // 빈값이 아니면 이동 + if let mySnack = self.mySnack { + mySnack.title = titleTextField + mySnack.text = contextTextView.text + mySnack.assiURL = urlTextView.text + snackManager.updateToDo(newSnackData: mySnack) { + print("업데이트 완료") + // 다시 전화면으로 돌아가기 + self.navigationController?.popViewController(animated: true) + } + } else { + snackManager.saveToDoData(title: titleTextField, text: contextTextView.text, photo: nil, categorie: "클래스", assiUrl: urlTextView.text){ + print("생성 완료") + print(titleTextField!) + } + //세이브되면 디테일 페이지 이동 + let storyboard = UIStoryboard(name: "DetailViewStoryboard", bundle: nil) + let vc = storyboard.instantiateViewController(withIdentifier: "DetailViewStoryboard") as! DetailViewController + + vc.mainTitle = titleTextField + vc.content = contextTextView.text + vc.url = urlTextView.text + vc.category = "클래스" + NotificationCenter.default.post(name: NSNotification.Name("RequestProgressUpdate"), object: nil) + self.navigationController?.pushViewController(vc, animated: true) + + +// 바로 셀있는 뷰로 이동 +// guard let viewControllerStack = self.navigationController?.viewControllers else { return } +// +// for viewController in viewControllerStack { +// if let vc = viewController as? TestViewController { +// self.navigationController?.pushViewController(vc, animated: true) +// } +// } + } + } + } + @objc func backButtonTapped() { + self.navigationController?.popViewController(animated: true) + } + + // MARK: - 제목 비어있으면 alert 창 + + private func titleIsEmptyAlertMessage() { + + let alert = UIAlertController(title: "제목이 비었습니다", message: "제목을 입력해야 완료가 됩니다", preferredStyle: .alert) + // UIAlertController를 통해 alert 창을 만듬/ preferredStyle: alert창이 어떻게 나오는지 설정 + let success = UIAlertAction(title: "확인", style: .default) { action in + print("확인 버튼이 눌렀습니다.") + } + let cancel = UIAlertAction(title: "취소", style: .cancel) { action in + print("취소 버튼이 눌렀습니다.") + } + alert.addAction(success) //addSubview랑 같은 것 + alert.addAction(cancel) + present(alert, animated: true, completion: nil) // 보여지게 함 + } + + // MARK: - scrollViewTapped() (스크롤뷰에서 다른 곳 터치 시 키보드 내림) + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + view.endEditing(true) + } + + + // MARK: - setupKeyboardEvent() (뷰에서 키보드 올림과 내림에서의 화면) + + func setupKeyboardEvent() { + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) + } + @objc func keyboardWillShow(_ sender: Notification) { + // keyboardFrame: 현재 동작하고 있는 이벤트에서 키보드의 frame을 받아옴 + // currentTextField: 현재 응답을 받고있는 UITextField를 알아냅니다. + guard let keyboardFrame = sender.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue, + let currentTextView = UIResponder.currentResponder as? UITextView else { return } + + // Y축으로 키보드의 상단 위치 + let keyboardTopY = keyboardFrame.cgRectValue.origin.y + // 현재 선택한 텍스트 필드의 Frame 값 + let convertedTextViewFrame = view.convert(currentTextView.frame, + from: currentTextView.superview) + // Y축으로 현재 텍스트 필드의 하단 위치 + let textFieldBottomY = convertedTextViewFrame.origin.y + convertedTextViewFrame.size.height + + // Y축으로 텍스트필드 하단 위치가 키보드 상단 위치보다 클 때 (즉, 텍스트필드가 키보드에 가려질 때가 되겠죠!) + if textFieldBottomY > keyboardTopY { + let textFieldTopY = convertedTextViewFrame.origin.y + // 노가다를 통해서 모든 기종에 적절한 크기를 설정함. + let newFrame = textFieldTopY - keyboardTopY/1.17 + view.frame.origin.y -= newFrame + } + } + + @objc func keyboardWillHide(_ sender: Notification) { + if view.frame.origin.y != 0 { + view.frame.origin.y = 0 + } + } + +} + +// MARK: - UITextViewDelegate + + +extension WriteViewController: UITextViewDelegate { + + //텍스트뷰 플레이스 홀더 + // 입력을 시작할때 + func textViewDidBeginEditing(_ textView: UITextView) { + if textView.text == "여기에 입력하세요." { + textView.text = nil + textView.textColor = .black + } + + } + + // 입력이 끝났을때 + func textViewDidEndEditing(_ textView: UITextView) { + // 비어있으면 다시 플레이스 홀더처럼 입력하기 위해서 조건 확인 + if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + textView.text = "여기에 입력하세요." + textView.textColor = .lightGray + } + } + +} + + +// MARK: - UITextFieldDelegate + +extension WriteViewController: UITextFieldDelegate { + + //제목에서 키보드 다음버튼 누르면 아래 텍스트뷰(내용칸)으로 커서 이동 + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if textField == self.titleTextField { + self.contextTextView.becomeFirstResponder() + } + return true + } +} + +extension UIResponder { + + private struct Static { + static weak var responder: UIResponder? + } + + static var currentResponder: UIResponder? { + Static.responder = nil + UIApplication.shared.sendAction(#selector(UIResponder.trap), to: nil, from: nil, for: nil) + return Static.responder + } + + @objc private func trap() { + Static.responder = self + } +} diff --git a/AppleSnack/WriteViewStoryboard.storyboard b/AppleSnack/WriteViewStoryboard.storyboard new file mode 100644 index 0000000..1bc673d --- /dev/null +++ b/AppleSnack/WriteViewStoryboard.storyboard @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2023-08-16 22:56:20.415771+0900 AppleSnack[19193:594042] [HardwareKeyboard] -[UIApplication getKeyboardDevicePropertiesForSenderID:shouldUpdate:usingSyntheticEvent:], failed to fetch device property for senderID (778835616971358209) use primary keyboard info instead. +2023-08-16 22:56:20.416720+0900 AppleSnack[19193:594042] [HardwareKeyboard] -[UIApplication getKeyboardDevicePropertiesForSenderID:shouldUpdate:usingSyntheticEvent:], failed to fetch device property for senderID (778835616971358209) use primary keyboard info instead. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +