From 76b0d096d2bc6d364b391e7e24f9dc51f0f2791c Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Mon, 28 Aug 2023 19:11:48 +0900 Subject: [PATCH 01/38] =?UTF-8?q?feat:=20=EC=BD=94=EB=93=9C=EB=B2=A0?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=EB=A1=9C=20UI=EB=A5=BC=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EB=93=9C=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20?= =?UTF-8?q?SceneDelegate=EC=97=90=20rootViewController=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 14 -------------- Diary/AppDelegate.swift | 4 +--- Diary/Base.lproj/Main.storyboard | 24 ------------------------ Diary/Info.plist | 2 -- Diary/SceneDelegate.swift | 14 ++++++++------ Diary/ViewController.swift | 5 +---- 6 files changed, 10 insertions(+), 53 deletions(-) delete mode 100644 Diary/Base.lproj/Main.storyboard diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index da144935d..63e972788 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* ViewController.swift */; }; - C739AE2C284DF28600741E8F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C739AE2A284DF28600741E8F /* Main.storyboard */; }; C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */; }; C739AE31284DF28600741E8F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C739AE30284DF28600741E8F /* Assets.xcassets */; }; C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C739AE32284DF28600741E8F /* LaunchScreen.storyboard */; }; @@ -21,7 +20,6 @@ C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; C739AE28284DF28600741E8F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - C739AE2B284DF28600741E8F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; C739AE2E284DF28600741E8F /* Diary.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Diary.xcdatamodel; sourceTree = ""; }; C739AE30284DF28600741E8F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C739AE33284DF28600741E8F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -61,7 +59,6 @@ C739AE24284DF28600741E8F /* AppDelegate.swift */, C739AE26284DF28600741E8F /* SceneDelegate.swift */, C739AE28284DF28600741E8F /* ViewController.swift */, - C739AE2A284DF28600741E8F /* Main.storyboard */, C739AE30284DF28600741E8F /* Assets.xcassets */, C739AE32284DF28600741E8F /* LaunchScreen.storyboard */, C739AE35284DF28600741E8F /* Info.plist */, @@ -130,7 +127,6 @@ files = ( C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */, C739AE31284DF28600741E8F /* Assets.xcassets in Resources */, - C739AE2C284DF28600741E8F /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -151,14 +147,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - C739AE2A284DF28600741E8F /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - C739AE2B284DF28600741E8F /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; C739AE32284DF28600741E8F /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -297,7 +285,6 @@ INFOPLIST_FILE = Diary/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"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -325,7 +312,6 @@ INFOPLIST_FILE = Diary/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"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; diff --git a/Diary/AppDelegate.swift b/Diary/AppDelegate.swift index 7efc2f7c0..f9b5a8a4b 100644 --- a/Diary/AppDelegate.swift +++ b/Diary/AppDelegate.swift @@ -8,9 +8,7 @@ import UIKit import CoreData @main -class AppDelegate: UIResponder, UIApplicationDelegate { - - +final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. diff --git a/Diary/Base.lproj/Main.storyboard b/Diary/Base.lproj/Main.storyboard deleted file mode 100644 index 25a763858..000000000 --- a/Diary/Base.lproj/Main.storyboard +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Diary/Info.plist b/Diary/Info.plist index dd3c9afda..0eb786dc1 100644 --- a/Diary/Info.plist +++ b/Diary/Info.plist @@ -15,8 +15,6 @@ Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main diff --git a/Diary/SceneDelegate.swift b/Diary/SceneDelegate.swift index c739cbc38..a3ba9636e 100644 --- a/Diary/SceneDelegate.swift +++ b/Diary/SceneDelegate.swift @@ -6,16 +6,18 @@ import UIKit -class SceneDelegate: UIResponder, UIWindowSceneDelegate { +final 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 _ = (scene as? UIWindowScene) else { return } + guard let windowScene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: windowScene) + let mainViewController: ViewController = ViewController() + + let navigationController = UINavigationController(rootViewController: mainViewController) + window?.rootViewController = navigationController + window?.makeKeyAndVisible() } func sceneDidDisconnect(_ scene: UIScene) { diff --git a/Diary/ViewController.swift b/Diary/ViewController.swift index dd724e13a..d56ea716d 100644 --- a/Diary/ViewController.swift +++ b/Diary/ViewController.swift @@ -6,13 +6,10 @@ import UIKit -class ViewController: UIViewController { +final class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. } - - } From fab5b8fa815fbe6c9721f7ad37d5772434ae20af Mon Sep 17 00:00:00 2001 From: idinaloq Date: Mon, 28 Aug 2023 19:42:16 +0900 Subject: [PATCH 02/38] =?UTF-8?q?feat:swiftLint=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AppDelegate, SceneDelegate 파일 swiftLint 제외 --- .swiftlint.yml | 3 +++ Diary.xcodeproj/project.pbxproj | 35 +++++++++++++++++++++++++++++---- Diary/ViewController.swift | 1 - 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 .swiftlint.yml diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 000000000..8e7dd4857 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,3 @@ +excluded: + - Diary/AppDelegate.swift + - Diary/SceneDelegate.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 63e972788..eab2810e1 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */; }; C739AE31284DF28600741E8F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C739AE30284DF28600741E8F /* Assets.xcassets */; }; C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C739AE32284DF28600741E8F /* LaunchScreen.storyboard */; }; + DC3EA1662A9CAE8400986F72 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -24,6 +25,7 @@ C739AE30284DF28600741E8F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C739AE33284DF28600741E8F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; C739AE35284DF28600741E8F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -40,6 +42,7 @@ C739AE18284DF28600741E8F = { isa = PBXGroup; children = ( + DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */, C739AE23284DF28600741E8F /* Diary */, C739AE22284DF28600741E8F /* Products */, ); @@ -74,6 +77,7 @@ isa = PBXNativeTarget; buildConfigurationList = C739AE38284DF28600741E8F /* Build configuration list for PBXNativeTarget "Diary" */; buildPhases = ( + DC3EA1622A9CAAAF00986F72 /* SwiftLint Script */, C739AE1D284DF28600741E8F /* Sources */, C739AE1E284DF28600741E8F /* Frameworks */, C739AE1F284DF28600741E8F /* Resources */, @@ -125,6 +129,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + DC3EA1662A9CAE8400986F72 /* .swiftlint.yml in Resources */, C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */, C739AE31284DF28600741E8F /* Assets.xcassets in Resources */, ); @@ -132,6 +137,28 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + DC3EA1622A9CAAAF00986F72 /* SwiftLint Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "SwiftLint Script"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nexport PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ C739AE1D284DF28600741E8F /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -209,7 +236,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -264,7 +291,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -287,7 +314,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -314,7 +341,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Diary/ViewController.swift b/Diary/ViewController.swift index d56ea716d..1a116c89d 100644 --- a/Diary/ViewController.swift +++ b/Diary/ViewController.swift @@ -12,4 +12,3 @@ final class ViewController: UIViewController { super.viewDidLoad() } } - From 5d3d09428d0266eb38573c2784a8dbb6ded6f893 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Tue, 29 Aug 2023 09:46:23 +0900 Subject: [PATCH 03/38] =?UTF-8?q?feat:=20CollectionView=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20ListCell=20=EA=B5=AC=ED=98=84=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swiftlint.yml | 4 +- Diary.xcodeproj/project.pbxproj | 38 ++++++++++++++++--- Diary/Controller/ViewController.swift | 22 +++++++++++ Diary/{ => Resource}/AppDelegate.swift | 0 .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../Assets.xcassets/Contents.json | 0 Diary/{ => Resource}/SceneDelegate.swift | 4 +- .../Base.lproj/LaunchScreen.storyboard | 0 Diary/View/DiaryCollectionViewListCell.swift | 32 ++++++++++++++++ Diary/ViewController.swift | 14 ------- 11 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 Diary/Controller/ViewController.swift rename Diary/{ => Resource}/AppDelegate.swift (100%) rename Diary/{ => Resource}/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename Diary/{ => Resource}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename Diary/{ => Resource}/Assets.xcassets/Contents.json (100%) rename Diary/{ => Resource}/SceneDelegate.swift (96%) rename Diary/{ => View}/Base.lproj/LaunchScreen.storyboard (100%) create mode 100644 Diary/View/DiaryCollectionViewListCell.swift delete mode 100644 Diary/ViewController.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 8e7dd4857..f8413c493 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,3 @@ excluded: - - Diary/AppDelegate.swift - - Diary/SceneDelegate.swift + - Diary/Resource/AppDelegate.swift + - Diary/Resource/SceneDelegate.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index eab2810e1..c81179880 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* ViewController.swift */; }; @@ -17,6 +18,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewListCell.swift; sourceTree = ""; }; C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -39,6 +41,33 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3BBC988C2A9D67EC0047DE81 /* Resource */ = { + isa = PBXGroup; + children = ( + C739AE24284DF28600741E8F /* AppDelegate.swift */, + C739AE26284DF28600741E8F /* SceneDelegate.swift */, + C739AE30284DF28600741E8F /* Assets.xcassets */, + ); + path = Resource; + sourceTree = ""; + }; + 3BBC988D2A9D68290047DE81 /* Controller */ = { + isa = PBXGroup; + children = ( + C739AE28284DF28600741E8F /* ViewController.swift */, + ); + path = Controller; + sourceTree = ""; + }; + 3BBC988E2A9D683C0047DE81 /* View */ = { + isa = PBXGroup; + children = ( + C739AE32284DF28600741E8F /* LaunchScreen.storyboard */, + 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */, + ); + path = View; + sourceTree = ""; + }; C739AE18284DF28600741E8F = { isa = PBXGroup; children = ( @@ -59,11 +88,9 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( - C739AE24284DF28600741E8F /* AppDelegate.swift */, - C739AE26284DF28600741E8F /* SceneDelegate.swift */, - C739AE28284DF28600741E8F /* ViewController.swift */, - C739AE30284DF28600741E8F /* Assets.xcassets */, - C739AE32284DF28600741E8F /* LaunchScreen.storyboard */, + 3BBC988E2A9D683C0047DE81 /* View */, + 3BBC988D2A9D68290047DE81 /* Controller */, + 3BBC988C2A9D67EC0047DE81 /* Resource */, C739AE35284DF28600741E8F /* Info.plist */, C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */, ); @@ -168,6 +195,7 @@ C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, + 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Diary/Controller/ViewController.swift b/Diary/Controller/ViewController.swift new file mode 100644 index 000000000..d34140851 --- /dev/null +++ b/Diary/Controller/ViewController.swift @@ -0,0 +1,22 @@ +// +// Diary - ViewController.swift +// Created by yagom. +// Copyright © yagom. All rights reserved. +// + +import UIKit + +final class DiaryListViewController: UIViewController { + private let collectionView: UICollectionView = { + let configuration = UICollectionLayoutListConfiguration(appearance: .plain) + let layout = UICollectionViewCompositionalLayout.list(using: configuration) + let view = UICollectionView(frame: .zero, collectionViewLayout: layout) + view.translatesAutoresizingMaskIntoConstraints = false +// view.register(<#T##cellClass: AnyClass?##AnyClass?#>, forCellWithReuseIdentifier: <#T##String#>) + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + } +} diff --git a/Diary/AppDelegate.swift b/Diary/Resource/AppDelegate.swift similarity index 100% rename from Diary/AppDelegate.swift rename to Diary/Resource/AppDelegate.swift diff --git a/Diary/Assets.xcassets/AccentColor.colorset/Contents.json b/Diary/Resource/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Diary/Assets.xcassets/AccentColor.colorset/Contents.json rename to Diary/Resource/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Diary/Assets.xcassets/AppIcon.appiconset/Contents.json b/Diary/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Diary/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Diary/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Diary/Assets.xcassets/Contents.json b/Diary/Resource/Assets.xcassets/Contents.json similarity index 100% rename from Diary/Assets.xcassets/Contents.json rename to Diary/Resource/Assets.xcassets/Contents.json diff --git a/Diary/SceneDelegate.swift b/Diary/Resource/SceneDelegate.swift similarity index 96% rename from Diary/SceneDelegate.swift rename to Diary/Resource/SceneDelegate.swift index a3ba9636e..6195830bc 100644 --- a/Diary/SceneDelegate.swift +++ b/Diary/Resource/SceneDelegate.swift @@ -13,7 +13,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) - let mainViewController: ViewController = ViewController() + let mainViewController: UIViewController = DiaryListViewController() let navigationController = UINavigationController(rootViewController: mainViewController) window?.rootViewController = navigationController @@ -50,7 +50,5 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Save changes in the application's managed object context when the application transitions to the background. (UIApplication.shared.delegate as? AppDelegate)?.saveContext() } - - } diff --git a/Diary/Base.lproj/LaunchScreen.storyboard b/Diary/View/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from Diary/Base.lproj/LaunchScreen.storyboard rename to Diary/View/Base.lproj/LaunchScreen.storyboard diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift new file mode 100644 index 000000000..8269f4916 --- /dev/null +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -0,0 +1,32 @@ +// +// DiaryCollectionViewCell.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/29. +// + +import UIKit + +class DiaryCollectionViewListCell: UICollectionViewListCell { + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "타이블레이블" + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 1 + return label + }() + private let dateLabel: UILabel = { + let label = UILabel() + label.text = "데이트레이블" + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 1 + return label + }() + private let previewLabel: UILabel = { + let label = UILabel() + label.text = "프리뷰레이블" + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 1 + return label + }() +} diff --git a/Diary/ViewController.swift b/Diary/ViewController.swift deleted file mode 100644 index 1a116c89d..000000000 --- a/Diary/ViewController.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Diary - ViewController.swift -// Created by yagom. -// Copyright © yagom. All rights reserved. -// - -import UIKit - -final class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - } -} From 93d6c80ea0829b871c4d44e9c5022ae51a93c213 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Tue, 29 Aug 2023 10:44:12 +0900 Subject: [PATCH 04/38] =?UTF-8?q?feat:=20DiaryCollectionViewListCell=20?= =?UTF-8?q?=EC=98=A4=ED=86=A0=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ViewController.swift -> DiaryListViewController.swift 네이밍 변경 --- Diary.xcodeproj/project.pbxproj | 8 +-- .../Controller/DiaryListViewController.swift | 53 ++++++++++++++++ Diary/Controller/ViewController.swift | 22 ------- Diary/View/DiaryCollectionViewListCell.swift | 62 +++++++++++++++++-- 4 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 Diary/Controller/DiaryListViewController.swift delete mode 100644 Diary/Controller/ViewController.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index c81179880..71ed362b8 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; - C739AE29284DF28600741E8F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* ViewController.swift */; }; + C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryListViewController.swift */; }; C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */; }; C739AE31284DF28600741E8F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C739AE30284DF28600741E8F /* Assets.xcassets */; }; C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C739AE32284DF28600741E8F /* LaunchScreen.storyboard */; }; @@ -22,7 +22,7 @@ C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - C739AE28284DF28600741E8F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + C739AE28284DF28600741E8F /* DiaryListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryListViewController.swift; sourceTree = ""; }; C739AE2E284DF28600741E8F /* Diary.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Diary.xcdatamodel; sourceTree = ""; }; C739AE30284DF28600741E8F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C739AE33284DF28600741E8F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -54,7 +54,7 @@ 3BBC988D2A9D68290047DE81 /* Controller */ = { isa = PBXGroup; children = ( - C739AE28284DF28600741E8F /* ViewController.swift */, + C739AE28284DF28600741E8F /* DiaryListViewController.swift */, ); path = Controller; sourceTree = ""; @@ -191,7 +191,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C739AE29284DF28600741E8F /* ViewController.swift in Sources */, + C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */, C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift new file mode 100644 index 000000000..ff08975ea --- /dev/null +++ b/Diary/Controller/DiaryListViewController.swift @@ -0,0 +1,53 @@ +// +// Diary - DiaryListViewController.swift +// Created by yagom. +// Copyright © yagom. All rights reserved. +// + +import UIKit + +final class DiaryListViewController: UIViewController { + private let collectionView: UICollectionView = { + let configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) + let layout = UICollectionViewCompositionalLayout.list(using: configuration) + let view = UICollectionView(frame: .zero, collectionViewLayout: layout) + view.translatesAutoresizingMaskIntoConstraints = false + view.register(DiaryCollectionViewListCell.self, forCellWithReuseIdentifier: DiaryCollectionViewListCell.identifier) + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + collectionView.dataSource = self + collectionView.delegate = self + configureUI() + configureAutoLayout() + } + private func configureUI() { + view.addSubview(collectionView) + view.backgroundColor = .systemBackground + } + private func configureAutoLayout() { + let safeArea = view.safeAreaLayoutGuide + NSLayoutConstraint.activate([ + collectionView.topAnchor.constraint(equalTo: safeArea.topAnchor), + collectionView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor), + collectionView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor), + collectionView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor) + ]) + } +} + +extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DiaryCollectionViewListCell.identifier, for: indexPath) + + return cell + } + + +} diff --git a/Diary/Controller/ViewController.swift b/Diary/Controller/ViewController.swift deleted file mode 100644 index d34140851..000000000 --- a/Diary/Controller/ViewController.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Diary - ViewController.swift -// Created by yagom. -// Copyright © yagom. All rights reserved. -// - -import UIKit - -final class DiaryListViewController: UIViewController { - private let collectionView: UICollectionView = { - let configuration = UICollectionLayoutListConfiguration(appearance: .plain) - let layout = UICollectionViewCompositionalLayout.list(using: configuration) - let view = UICollectionView(frame: .zero, collectionViewLayout: layout) - view.translatesAutoresizingMaskIntoConstraints = false -// view.register(<#T##cellClass: AnyClass?##AnyClass?#>, forCellWithReuseIdentifier: <#T##String#>) - return view - }() - - override func viewDidLoad() { - super.viewDidLoad() - } -} diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 8269f4916..ccc7a647c 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -8,25 +8,77 @@ import UIKit class DiaryCollectionViewListCell: UICollectionViewListCell { + static let identifier: String = "DiaryCollectionViewListCell" private let titleLabel: UILabel = { - let label = UILabel() - label.text = "타이블레이블" + let label: UILabel = UILabel() + label.text = "타이틀레이블" label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 + label.font = .preferredFont(forTextStyle: .title2) return label }() private let dateLabel: UILabel = { - let label = UILabel() - label.text = "데이트레이블" + let label: UILabel = UILabel() + label.text = "2020년 12월 23일" label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 + label.font = .preferredFont(forTextStyle: .body) return label }() private let previewLabel: UILabel = { - let label = UILabel() + let label: UILabel = UILabel() label.text = "프리뷰레이블" label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 + label.textAlignment = .left + label.setContentHuggingPriority(.init(200), for: .horizontal) + label.font = .preferredFont(forTextStyle: .body) return label }() + private let titleLabelStackView: UIStackView = { + let stackView: UIStackView = UIStackView() + stackView.axis = .vertical + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + private let dateAndPreviewStackView: UIStackView = { + let stackView: UIStackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 8 + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + override init(frame: CGRect) { + super.init(frame: frame) + configureUI() + configureAutoLayout() + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + private func configureUI() { + titleLabelStackView.addArrangedSubview(titleLabel) +// titleLabelStackView.addArrangedSubview(dateAndPreviewStackView) + dateAndPreviewStackView.addArrangedSubview(dateLabel) + dateAndPreviewStackView.addArrangedSubview(previewLabel) + contentView.addSubview(titleLabelStackView) + contentView.addSubview(dateAndPreviewStackView) + + self.accessories = [.disclosureIndicator()] + } + private func configureAutoLayout() { + NSLayoutConstraint.activate([ + titleLabelStackView.topAnchor.constraint(equalTo: contentView.topAnchor), + titleLabelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + titleLabelStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + titleLabelStackView.bottomAnchor.constraint(equalTo: dateAndPreviewStackView.topAnchor, constant: -8), + + + dateAndPreviewStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + dateAndPreviewStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + dateAndPreviewStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + + + ]) + } } From 9c6dcf7602c7064c7eb614c48360f065c29cdded Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Tue, 29 Aug 2023 12:40:14 +0900 Subject: [PATCH 05/38] =?UTF-8?q?feat:=20DiaryEntity=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20JSON=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=94=94=EC=BD=94=EB=94=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swiftlint.yml | 6 ++ Diary.xcodeproj/project.pbxproj | 12 +++ .../Controller/DiaryListViewController.swift | 25 +++++- Diary/Model/DiaryEntity.swift | 17 ++++ .../sample.dataset/Contents.json | 13 ++++ .../sample.dataset/sample.json | 77 +++++++++++++++++++ Diary/View/DiaryCollectionViewListCell.swift | 30 ++++++-- 7 files changed, 168 insertions(+), 12 deletions(-) create mode 100644 Diary/Model/DiaryEntity.swift create mode 100644 Diary/Resource/Assets.xcassets/sample.dataset/Contents.json create mode 100644 Diary/Resource/Assets.xcassets/sample.dataset/sample.json diff --git a/.swiftlint.yml b/.swiftlint.yml index f8413c493..f37688072 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,3 +1,9 @@ +disabled_rules: + - trailing_whitespace + - line_length + excluded: - Diary/Resource/AppDelegate.swift - Diary/Resource/SceneDelegate.swift + + diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 71ed362b8..5d605c3a4 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */; }; + 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryListViewController.swift */; }; @@ -19,6 +20,7 @@ /* Begin PBXFileReference section */ 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewListCell.swift; sourceTree = ""; }; + 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryEntity.swift; sourceTree = ""; }; C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -68,6 +70,14 @@ path = View; sourceTree = ""; }; + 3BBC98922A9D897B0047DE81 /* Model */ = { + isa = PBXGroup; + children = ( + 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */, + ); + path = Model; + sourceTree = ""; + }; C739AE18284DF28600741E8F = { isa = PBXGroup; children = ( @@ -88,6 +98,7 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( + 3BBC98922A9D897B0047DE81 /* Model */, 3BBC988E2A9D683C0047DE81 /* View */, 3BBC988D2A9D68290047DE81 /* Controller */, 3BBC988C2A9D67EC0047DE81 /* Resource */, @@ -196,6 +207,7 @@ C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, + 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index ff08975ea..4bd52513a 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -7,26 +7,33 @@ import UIKit final class DiaryListViewController: UIViewController { + private var diaryEntity: [DiaryEntity]? + private let collectionView: UICollectionView = { let configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) let layout = UICollectionViewCompositionalLayout.list(using: configuration) let view = UICollectionView(frame: .zero, collectionViewLayout: layout) view.translatesAutoresizingMaskIntoConstraints = false view.register(DiaryCollectionViewListCell.self, forCellWithReuseIdentifier: DiaryCollectionViewListCell.identifier) + return view }() override func viewDidLoad() { super.viewDidLoad() + decodeData() collectionView.dataSource = self collectionView.delegate = self configureUI() configureAutoLayout() + } + private func configureUI() { view.addSubview(collectionView) view.backgroundColor = .systemBackground } + private func configureAutoLayout() { let safeArea = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ @@ -36,18 +43,28 @@ final class DiaryListViewController: UIViewController { collectionView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor) ]) } + + private func decodeData() { + guard let asset = NSDataAsset(name: "sample") else { + return + } + + let decodedData = try? JSONDecoder().decode([DiaryEntity].self, from: asset.data) + diaryEntity = decodedData + } } extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return 1 + return diaryEntity?.count ?? 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DiaryCollectionViewListCell.identifier, for: indexPath) + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DiaryCollectionViewListCell.identifier, for: indexPath) as? DiaryCollectionViewListCell else { + return UICollectionViewCell() + } + cell.configureLabel(diaryEntity![indexPath.item]) return cell } - - } diff --git a/Diary/Model/DiaryEntity.swift b/Diary/Model/DiaryEntity.swift new file mode 100644 index 000000000..842b8ff06 --- /dev/null +++ b/Diary/Model/DiaryEntity.swift @@ -0,0 +1,17 @@ +// +// DiaryEntity.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/29. +// + +struct DiaryEntity: Decodable { + let title: String + let body: String + let createdAt: Int + + enum CodingKeys: String, CodingKey { + case title, body + case createdAt = "created_at" + } +} diff --git a/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json b/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json new file mode 100644 index 000000000..86c8c9c95 --- /dev/null +++ b/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json @@ -0,0 +1,13 @@ +{ + "data" : [ + { + "filename" : "sample.json", + "idiom" : "universal", + "universal-type-identifier" : "public.json" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Diary/Resource/Assets.xcassets/sample.dataset/sample.json b/Diary/Resource/Assets.xcassets/sample.dataset/sample.json new file mode 100644 index 000000000..6746d8848 --- /dev/null +++ b/Diary/Resource/Assets.xcassets/sample.dataset/sample.json @@ -0,0 +1,77 @@ +[ + { + "title": "똘기떵이호치새초미자축인묘", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "드라고요롱이마초미미진사오미", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "몽키키키강달찡찡신유술해", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "네번째", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "승리는 우리의 것", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "호롤ㄹ로롤롤로로로롤롤로롤롤ㄹ롤롤 나방이 홓ㄹ로롤롤ㄹ로로로ㅗ롤로롤", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "우주 외계인 그는 무서운 암흑대왕", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "하나면 하나지 둘이겠느냐~", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "더 내려가봐유?", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + }, + { + "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", + "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", + "created_at": 1608651333 + } +] \ No newline at end of file diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index ccc7a647c..30c7dc49a 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -7,55 +7,64 @@ import UIKit -class DiaryCollectionViewListCell: UICollectionViewListCell { +final class DiaryCollectionViewListCell: UICollectionViewListCell { static let identifier: String = "DiaryCollectionViewListCell" + private let titleLabel: UILabel = { let label: UILabel = UILabel() - label.text = "타이틀레이블" label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 label.font = .preferredFont(forTextStyle: .title2) + return label }() + private let dateLabel: UILabel = { let label: UILabel = UILabel() - label.text = "2020년 12월 23일" label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 label.font = .preferredFont(forTextStyle: .body) + return label }() + private let previewLabel: UILabel = { let label: UILabel = UILabel() - label.text = "프리뷰레이블" label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 label.textAlignment = .left - label.setContentHuggingPriority(.init(200), for: .horizontal) label.font = .preferredFont(forTextStyle: .body) + return label }() + private let titleLabelStackView: UIStackView = { let stackView: UIStackView = UIStackView() stackView.axis = .vertical stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView }() + private let dateAndPreviewStackView: UIStackView = { let stackView: UIStackView = UIStackView() stackView.axis = .horizontal stackView.spacing = 8 stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView }() + override init(frame: CGRect) { super.init(frame: frame) configureUI() configureAutoLayout() } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + private func configureUI() { titleLabelStackView.addArrangedSubview(titleLabel) // titleLabelStackView.addArrangedSubview(dateAndPreviewStackView) @@ -66,19 +75,24 @@ class DiaryCollectionViewListCell: UICollectionViewListCell { self.accessories = [.disclosureIndicator()] } + private func configureAutoLayout() { NSLayoutConstraint.activate([ + dateLabel.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.3), titleLabelStackView.topAnchor.constraint(equalTo: contentView.topAnchor), titleLabelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), titleLabelStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), titleLabelStackView.bottomAnchor.constraint(equalTo: dateAndPreviewStackView.topAnchor, constant: -8), - dateAndPreviewStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), dateAndPreviewStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), dateAndPreviewStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) - - ]) } + + func configureLabel(_ data: DiaryEntity) { + titleLabel.text = data.title + dateLabel.text = String(data.createdAt) + previewLabel.text = data.body + } } From 0455cbdfe2d05a1f531f60a13f251d438587701c Mon Sep 17 00:00:00 2001 From: idinaloq Date: Tue, 29 Aug 2023 15:02:00 +0900 Subject: [PATCH 06/38] =?UTF-8?q?feat:=20=EB=82=A0=EC=A7=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1,=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98?= =?UTF-8?q?=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/DiaryListViewController.swift | 26 +++++++++++++++++-- Diary/View/DiaryCollectionViewListCell.swift | 14 ++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 4bd52513a..4844bdc1f 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -24,6 +24,7 @@ final class DiaryListViewController: UIViewController { decodeData() collectionView.dataSource = self collectionView.delegate = self + configureNavigation() configureUI() configureAutoLayout() @@ -52,18 +53,39 @@ final class DiaryListViewController: UIViewController { let decodedData = try? JSONDecoder().decode([DiaryEntity].self, from: asset.data) diaryEntity = decodedData } + + private func configureNavigation() { + navigationItem.title = "일기장" + + let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(abc)) + self.navigationItem.rightBarButtonItem = addDiary + + } + + @objc private func abc() { + + } } extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return diaryEntity?.count ?? 0 + guard let diaryEntity = diaryEntity else { + return 0 + } + + return diaryEntity.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DiaryCollectionViewListCell.identifier, for: indexPath) as? DiaryCollectionViewListCell else { return UICollectionViewCell() } - cell.configureLabel(diaryEntity![indexPath.item]) + + guard let diaryEntity = diaryEntity else { + return UICollectionViewCell() + } + + cell.configureLabel(diaryEntity[indexPath.item]) return cell } diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 30c7dc49a..d5e09be01 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -24,6 +24,7 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 label.font = .preferredFont(forTextStyle: .body) + label.setContentCompressionResistancePriority(.init(800), for: .horizontal) return label }() @@ -78,7 +79,6 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { private func configureAutoLayout() { NSLayoutConstraint.activate([ - dateLabel.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.3), titleLabelStackView.topAnchor.constraint(equalTo: contentView.topAnchor), titleLabelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), titleLabelStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), @@ -91,8 +91,18 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { } func configureLabel(_ data: DiaryEntity) { + let dateFormatter: DateFormatter = { + let dateFormatter: DateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.timeZone = TimeZone(abbreviation: "KST") + dateFormatter.dateStyle = .long + + return dateFormatter + }() + let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) + titleLabel.text = data.title - dateLabel.text = String(data.createdAt) + dateLabel.text = dateFormatter.string(from: date) previewLabel.text = data.body } } From 69f5b06c9d85a357724b1e70b6f396dc4490a7a7 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Tue, 29 Aug 2023 15:39:03 +0900 Subject: [PATCH 07/38] =?UTF-8?q?feat:=20=EB=A0=88=EC=9D=B4=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=A1=B0=EC=A0=88=20=EB=B0=8F=20DiaryDetailViewCon?= =?UTF-8?q?troller=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 4 +++ .../DiaryDetailViewController.swift | 25 +++++++++++++++++++ .../Controller/DiaryListViewController.swift | 11 ++++++++ Diary/View/DiaryCollectionViewListCell.swift | 10 ++++---- 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 Diary/Controller/DiaryDetailViewController.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 5d605c3a4..3972e8658 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */; }; 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */; }; + 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryListViewController.swift */; }; @@ -21,6 +22,7 @@ /* Begin PBXFileReference section */ 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewListCell.swift; sourceTree = ""; }; 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryEntity.swift; sourceTree = ""; }; + 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryDetailViewController.swift; sourceTree = ""; }; C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -57,6 +59,7 @@ isa = PBXGroup; children = ( C739AE28284DF28600741E8F /* DiaryListViewController.swift */, + 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */, ); path = Controller; sourceTree = ""; @@ -205,6 +208,7 @@ C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */, C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, + 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift new file mode 100644 index 000000000..805c66ff5 --- /dev/null +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -0,0 +1,25 @@ +// +// DiaryDetailViewController.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/29. +// + +import UIKit + +final class DiaryDetailViewController: UIViewController { + private var diaryEntity: [DiaryEntity]? + + override func viewDidLoad() { + super.viewDidLoad() + } + + init(data: [DiaryEntity]) { + self.diaryEntity = data + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 4844bdc1f..86a2b33f3 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -89,4 +89,15 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD return cell } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let diaryEntity = diaryEntity else { + return + } + + collectionView.deselectItem(at: indexPath, animated: true) + + let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(data: diaryEntity) + navigationController?.pushViewController(diaryDetailViewController, animated: true) + } } diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index d5e09be01..01b00d366 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -34,7 +34,7 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 label.textAlignment = .left - label.font = .preferredFont(forTextStyle: .body) + label.font = UIFont.systemFont(ofSize: 12) return label }() @@ -79,14 +79,14 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { private func configureAutoLayout() { NSLayoutConstraint.activate([ - titleLabelStackView.topAnchor.constraint(equalTo: contentView.topAnchor), - titleLabelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + titleLabelStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5), + titleLabelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), titleLabelStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), titleLabelStackView.bottomAnchor.constraint(equalTo: dateAndPreviewStackView.topAnchor, constant: -8), - dateAndPreviewStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + dateAndPreviewStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), dateAndPreviewStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - dateAndPreviewStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + dateAndPreviewStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5) ]) } From 075ec7b8d7073f217f3a1d180d04ceeb4369d252 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Tue, 29 Aug 2023 16:37:20 +0900 Subject: [PATCH 08/38] =?UTF-8?q?feat:=20DiaryDetailViewController=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 화면에 데이터 받아와서 표시, 수정가능하도록 추가 --- .../DiaryDetailViewController.swift | 33 +++++++++++++++++-- .../Controller/DiaryListViewController.swift | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 805c66ff5..67c9f192f 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -8,13 +8,23 @@ import UIKit final class DiaryDetailViewController: UIViewController { - private var diaryEntity: [DiaryEntity]? + private var diaryEntity: DiaryEntity? + private lazy var textView: UITextView = { + let view: UITextView = UITextView() + view.text = diaryEntity!.title + "\n\n" + diaryEntity!.body + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() override func viewDidLoad() { super.viewDidLoad() + + configureNavigation() + configureUI() + configureAutoLayout() } - init(data: [DiaryEntity]) { + init(data: DiaryEntity) { self.diaryEntity = data super.init(nibName: nil, bundle: nil) } @@ -22,4 +32,23 @@ final class DiaryDetailViewController: UIViewController { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func configureNavigation() { + navigationItem.title = "네이게이션타이틀" + } + + private func configureUI() { + view.addSubview(textView) + view.backgroundColor = .systemBackground + } + + private func configureAutoLayout() { + let safeArea = view.safeAreaLayoutGuide + NSLayoutConstraint.activate([ + textView.topAnchor.constraint(equalTo: safeArea.topAnchor), + textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor), + textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor), + textView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor) + ]) + } } diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 86a2b33f3..aecb05559 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -97,7 +97,7 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD collectionView.deselectItem(at: indexPath, animated: true) - let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(data: diaryEntity) + let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(data: diaryEntity[indexPath.item]) navigationController?.pushViewController(diaryDetailViewController, animated: true) } } From 43f55f0bfb7397bb8ccf539f9f85a4b6614adc8e Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Wed, 30 Aug 2023 10:17:31 +0900 Subject: [PATCH 09/38] =?UTF-8?q?feat:=20extension=EC=9C=BC=EB=A1=9C=20Dat?= =?UTF-8?q?eFormatter=20=ED=99=95=EC=9E=A5=20=EB=B0=8F=20=ED=82=A4?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 12 +++++++++ .../DiaryDetailViewController.swift | 26 ++++++++++++++++++- Diary/Extension/DateFormatter+.swift | 21 +++++++++++++++ Diary/View/DiaryCollectionViewListCell.swift | 13 ++-------- 4 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 Diary/Extension/DateFormatter+.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 3972e8658..65ea46ed3 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */; }; 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */; }; 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */; }; + 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryListViewController.swift */; }; @@ -23,6 +24,7 @@ 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewListCell.swift; sourceTree = ""; }; 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryEntity.swift; sourceTree = ""; }; 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryDetailViewController.swift; sourceTree = ""; }; + 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -81,6 +83,14 @@ path = Model; sourceTree = ""; }; + 3BBC98972A9E44820047DE81 /* Extension */ = { + isa = PBXGroup; + children = ( + 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */, + ); + path = Extension; + sourceTree = ""; + }; C739AE18284DF28600741E8F = { isa = PBXGroup; children = ( @@ -101,6 +111,7 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( + 3BBC98972A9E44820047DE81 /* Extension */, 3BBC98922A9D897B0047DE81 /* Model */, 3BBC988E2A9D683C0047DE81 /* View */, 3BBC988D2A9D68290047DE81 /* Controller */, @@ -209,6 +220,7 @@ C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */, + 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 67c9f192f..2cb11583b 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -9,6 +9,7 @@ import UIKit final class DiaryDetailViewController: UIViewController { private var diaryEntity: DiaryEntity? + private lazy var textView: UITextView = { let view: UITextView = UITextView() view.text = diaryEntity!.title + "\n\n" + diaryEntity!.body @@ -22,6 +23,7 @@ final class DiaryDetailViewController: UIViewController { configureNavigation() configureUI() configureAutoLayout() + setUpKeyboardEvent() } init(data: DiaryEntity) { @@ -34,7 +36,7 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - navigationItem.title = "네이게이션타이틀" + navigationItem.title = DateFormatter().formatDate(diaryEntity!) } private func configureUI() { @@ -51,4 +53,26 @@ final class DiaryDetailViewController: UIViewController { textView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor) ]) } + + private 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 private func keyboardWillShow(_ notification: Notification) { + guard let userInfo = notification.userInfo as NSDictionary?, + var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { + return + } + keyboardFrame = view.convert(keyboardFrame, from: nil) + var contentInset = textView.contentInset + contentInset.bottom = keyboardFrame.size.height + textView.contentInset = contentInset + textView.scrollIndicatorInsets = textView.contentInset + } + + @objc private func keyboardWillHide(_ notification: Notification) { + textView.contentInset = UIEdgeInsets.zero + textView.scrollIndicatorInsets = textView.contentInset + } } diff --git a/Diary/Extension/DateFormatter+.swift b/Diary/Extension/DateFormatter+.swift new file mode 100644 index 000000000..fc3eef22c --- /dev/null +++ b/Diary/Extension/DateFormatter+.swift @@ -0,0 +1,21 @@ +// +// DateFormatter+.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/30. +// + +import Foundation + +extension DateFormatter { + func formatDate(_ data: DiaryEntity) -> String { + let dateFormatter: DateFormatter = DateFormatter() + let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) + + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.timeZone = TimeZone(abbreviation: "KST") + dateFormatter.dateStyle = .long + + return dateFormatter.string(from: date) + } +} diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 01b00d366..8ddcd3b6c 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -10,6 +10,7 @@ import UIKit final class DiaryCollectionViewListCell: UICollectionViewListCell { static let identifier: String = "DiaryCollectionViewListCell" + private let titleLabel: UILabel = { let label: UILabel = UILabel() label.translatesAutoresizingMaskIntoConstraints = false @@ -91,18 +92,8 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { } func configureLabel(_ data: DiaryEntity) { - let dateFormatter: DateFormatter = { - let dateFormatter: DateFormatter = DateFormatter() - dateFormatter.locale = Locale(identifier: "ko_KR") - dateFormatter.timeZone = TimeZone(abbreviation: "KST") - dateFormatter.dateStyle = .long - - return dateFormatter - }() - let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) - titleLabel.text = data.title - dateLabel.text = dateFormatter.string(from: date) + dateLabel.text = DateFormatter().formatDate(data) previewLabel.text = data.body } } From 2f7b5f7feac1a4d796d969c56a1fc9e96282acca Mon Sep 17 00:00:00 2001 From: idinaloq Date: Wed, 30 Aug 2023 10:46:52 +0900 Subject: [PATCH 10/38] =?UTF-8?q?feat:=20=EB=8B=A4=EC=9D=B4=EC=96=B4?= =?UTF-8?q?=EB=A6=AC=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?NewDiaryViewController=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 4 ++ .../Controller/DiaryListViewController.swift | 6 +- Diary/Controller/NewDiaryViewController.swift | 67 +++++++++++++++++++ Diary/Extension/DateFormatter+.swift | 12 ++++ Diary/View/DiaryCollectionViewListCell.swift | 2 - 5 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 Diary/Controller/NewDiaryViewController.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 65ea46ed3..684e2765d 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ C739AE31284DF28600741E8F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C739AE30284DF28600741E8F /* Assets.xcassets */; }; C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C739AE32284DF28600741E8F /* LaunchScreen.storyboard */; }; DC3EA1662A9CAE8400986F72 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */; }; + DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -34,6 +35,7 @@ C739AE33284DF28600741E8F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; C739AE35284DF28600741E8F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; + DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDiaryViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,6 +64,7 @@ children = ( C739AE28284DF28600741E8F /* DiaryListViewController.swift */, 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */, + DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */, ); path = Controller; sourceTree = ""; @@ -223,6 +226,7 @@ 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, + DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */, 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index aecb05559..3898d5130 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -57,13 +57,15 @@ final class DiaryListViewController: UIViewController { private func configureNavigation() { navigationItem.title = "일기장" - let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(abc)) + let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(createNewDiary)) self.navigationItem.rightBarButtonItem = addDiary } - @objc private func abc() { + @objc private func createNewDiary() { + let newDiaryViewController: NewDiaryViewController = NewDiaryViewController() + navigationController?.pushViewController(newDiaryViewController, animated: true) } } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift new file mode 100644 index 000000000..6f71b79ae --- /dev/null +++ b/Diary/Controller/NewDiaryViewController.swift @@ -0,0 +1,67 @@ +// +// NewDiaryViewController.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/30. +// + +import UIKit + +class NewDiaryViewController: UIViewController { + private lazy var textView: UITextView = { + let view: UITextView = UITextView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + + configureNavigation() + configureUI() + configureAutoLayout() + setUpKeyboardEvent() + + } + + private func configureNavigation() { + navigationItem.title = DateFormatter.today + } + + private func configureUI() { + view.addSubview(textView) + view.backgroundColor = .systemBackground + } + + private func configureAutoLayout() { + let safeArea = view.safeAreaLayoutGuide + NSLayoutConstraint.activate([ + textView.topAnchor.constraint(equalTo: safeArea.topAnchor), + textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor), + textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor), + textView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor) + ]) + } + + private 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 private func keyboardWillShow(_ notification: Notification) { + guard let userInfo = notification.userInfo as NSDictionary?, + var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { + return + } + keyboardFrame = view.convert(keyboardFrame, from: nil) + var contentInset = textView.contentInset + contentInset.bottom = keyboardFrame.size.height + textView.contentInset = contentInset + textView.scrollIndicatorInsets = textView.contentInset + } + + @objc private func keyboardWillHide(_ notification: Notification) { + textView.contentInset = UIEdgeInsets.zero + textView.scrollIndicatorInsets = textView.contentInset + } +} diff --git a/Diary/Extension/DateFormatter+.swift b/Diary/Extension/DateFormatter+.swift index fc3eef22c..f23ae3c8d 100644 --- a/Diary/Extension/DateFormatter+.swift +++ b/Diary/Extension/DateFormatter+.swift @@ -8,6 +8,18 @@ import Foundation extension DateFormatter { + static var today: String { + let dateFormatter: DateFormatter = DateFormatter() + let date: Date = Date(timeIntervalSinceNow: 0) + + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.timeZone = TimeZone(abbreviation: "KST") + dateFormatter.dateStyle = .long + + return dateFormatter.string(from: date) + + } + func formatDate(_ data: DiaryEntity) -> String { let dateFormatter: DateFormatter = DateFormatter() let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 8ddcd3b6c..3aae12cae 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -10,7 +10,6 @@ import UIKit final class DiaryCollectionViewListCell: UICollectionViewListCell { static let identifier: String = "DiaryCollectionViewListCell" - private let titleLabel: UILabel = { let label: UILabel = UILabel() label.translatesAutoresizingMaskIntoConstraints = false @@ -69,7 +68,6 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { private func configureUI() { titleLabelStackView.addArrangedSubview(titleLabel) -// titleLabelStackView.addArrangedSubview(dateAndPreviewStackView) dateAndPreviewStackView.addArrangedSubview(dateLabel) dateAndPreviewStackView.addArrangedSubview(previewLabel) contentView.addSubview(titleLabelStackView) From 10ed1ff1b74c9e7c1a2af6634f0ee7a19ea9b302 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Wed, 30 Aug 2023 11:38:47 +0900 Subject: [PATCH 11/38] =?UTF-8?q?refactor:=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=94=EC=96=B4=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiaryDetailViewController.swift | 18 +++++--- .../Controller/DiaryListViewController.swift | 45 +++++++++---------- Diary/Controller/NewDiaryViewController.swift | 16 +++---- Diary/Extension/DateFormatter+.swift | 3 -- Diary/Model/DiaryEntity.swift | 3 +- Diary/View/DiaryCollectionViewListCell.swift | 10 +++-- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 2cb11583b..f155dc80f 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -12,17 +12,22 @@ final class DiaryDetailViewController: UIViewController { private lazy var textView: UITextView = { let view: UITextView = UITextView() - view.text = diaryEntity!.title + "\n\n" + diaryEntity!.body view.translatesAutoresizingMaskIntoConstraints = false + + guard let diaryEntity = diaryEntity else { + return view + } + + view.text = diaryEntity.title + "\n\n" + diaryEntity.body + return view }() override func viewDidLoad() { super.viewDidLoad() - configureNavigation() configureUI() - configureAutoLayout() + configureLayout() setUpKeyboardEvent() } @@ -44,7 +49,7 @@ final class DiaryDetailViewController: UIViewController { view.backgroundColor = .systemBackground } - private func configureAutoLayout() { + private func configureLayout() { let safeArea = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ textView.topAnchor.constraint(equalTo: safeArea.topAnchor), @@ -64,14 +69,15 @@ final class DiaryDetailViewController: UIViewController { var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return } - keyboardFrame = view.convert(keyboardFrame, from: nil) + var contentInset = textView.contentInset + keyboardFrame = view.convert(keyboardFrame, from: nil) contentInset.bottom = keyboardFrame.size.height textView.contentInset = contentInset textView.scrollIndicatorInsets = textView.contentInset } - @objc private func keyboardWillHide(_ notification: Notification) { + @objc private func keyboardWillHide() { textView.contentInset = UIEdgeInsets.zero textView.scrollIndicatorInsets = textView.contentInset } diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 3898d5130..ff1c5c8b8 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -22,12 +22,30 @@ final class DiaryListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() decodeData() - collectionView.dataSource = self - collectionView.delegate = self + configureCollectionView() configureNavigation() configureUI() - configureAutoLayout() + configureLayout() + } + + private func decodeData() { + guard let asset = NSDataAsset(name: "sample") else { + return + } + let decodedData = try? JSONDecoder().decode([DiaryEntity].self, from: asset.data) + diaryEntity = decodedData + } + + private func configureCollectionView() { + collectionView.dataSource = self + collectionView.delegate = self + } + + private func configureNavigation() { + let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(createNewDiary)) + self.navigationItem.rightBarButtonItem = addDiary + navigationItem.title = "일기장" } private func configureUI() { @@ -35,7 +53,7 @@ final class DiaryListViewController: UIViewController { view.backgroundColor = .systemBackground } - private func configureAutoLayout() { + private func configureLayout() { let safeArea = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: safeArea.topAnchor), @@ -44,27 +62,9 @@ final class DiaryListViewController: UIViewController { collectionView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor) ]) } - - private func decodeData() { - guard let asset = NSDataAsset(name: "sample") else { - return - } - - let decodedData = try? JSONDecoder().decode([DiaryEntity].self, from: asset.data) - diaryEntity = decodedData - } - - private func configureNavigation() { - navigationItem.title = "일기장" - - let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(createNewDiary)) - self.navigationItem.rightBarButtonItem = addDiary - } - @objc private func createNewDiary() { let newDiaryViewController: NewDiaryViewController = NewDiaryViewController() - navigationController?.pushViewController(newDiaryViewController, animated: true) } } @@ -98,7 +98,6 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD } collectionView.deselectItem(at: indexPath, animated: true) - let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(data: diaryEntity[indexPath.item]) navigationController?.pushViewController(diaryDetailViewController, animated: true) } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index 6f71b79ae..a43cfb5b1 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -7,21 +7,20 @@ import UIKit -class NewDiaryViewController: UIViewController { - private lazy var textView: UITextView = { +final class NewDiaryViewController: UIViewController { + private let textView: UITextView = { let view: UITextView = UITextView() view.translatesAutoresizingMaskIntoConstraints = false + return view }() override func viewDidLoad() { super.viewDidLoad() - configureNavigation() configureUI() - configureAutoLayout() + configureLayout() setUpKeyboardEvent() - } private func configureNavigation() { @@ -33,7 +32,7 @@ class NewDiaryViewController: UIViewController { view.backgroundColor = .systemBackground } - private func configureAutoLayout() { + private func configureLayout() { let safeArea = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ textView.topAnchor.constraint(equalTo: safeArea.topAnchor), @@ -53,14 +52,15 @@ class NewDiaryViewController: UIViewController { var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return } - keyboardFrame = view.convert(keyboardFrame, from: nil) + var contentInset = textView.contentInset + keyboardFrame = view.convert(keyboardFrame, from: nil) contentInset.bottom = keyboardFrame.size.height textView.contentInset = contentInset textView.scrollIndicatorInsets = textView.contentInset } - @objc private func keyboardWillHide(_ notification: Notification) { + @objc private func keyboardWillHide() { textView.contentInset = UIEdgeInsets.zero textView.scrollIndicatorInsets = textView.contentInset } diff --git a/Diary/Extension/DateFormatter+.swift b/Diary/Extension/DateFormatter+.swift index f23ae3c8d..373e29935 100644 --- a/Diary/Extension/DateFormatter+.swift +++ b/Diary/Extension/DateFormatter+.swift @@ -11,19 +11,16 @@ extension DateFormatter { static var today: String { let dateFormatter: DateFormatter = DateFormatter() let date: Date = Date(timeIntervalSinceNow: 0) - dateFormatter.locale = Locale(identifier: "ko_KR") dateFormatter.timeZone = TimeZone(abbreviation: "KST") dateFormatter.dateStyle = .long return dateFormatter.string(from: date) - } func formatDate(_ data: DiaryEntity) -> String { let dateFormatter: DateFormatter = DateFormatter() let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) - dateFormatter.locale = Locale(identifier: "ko_KR") dateFormatter.timeZone = TimeZone(abbreviation: "KST") dateFormatter.dateStyle = .long diff --git a/Diary/Model/DiaryEntity.swift b/Diary/Model/DiaryEntity.swift index 842b8ff06..e851c39c3 100644 --- a/Diary/Model/DiaryEntity.swift +++ b/Diary/Model/DiaryEntity.swift @@ -6,8 +6,7 @@ // struct DiaryEntity: Decodable { - let title: String - let body: String + let title, body: String let createdAt: Int enum CodingKeys: String, CodingKey { diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 3aae12cae..f6352eb9f 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -41,17 +41,17 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { private let titleLabelStackView: UIStackView = { let stackView: UIStackView = UIStackView() - stackView.axis = .vertical stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical return stackView }() private let dateAndPreviewStackView: UIStackView = { let stackView: UIStackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .horizontal stackView.spacing = 8 - stackView.translatesAutoresizingMaskIntoConstraints = false return stackView }() @@ -59,7 +59,7 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { override init(frame: CGRect) { super.init(frame: frame) configureUI() - configureAutoLayout() + configureLayout() } required init?(coder: NSCoder) { @@ -68,15 +68,17 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { private func configureUI() { titleLabelStackView.addArrangedSubview(titleLabel) + dateAndPreviewStackView.addArrangedSubview(dateLabel) dateAndPreviewStackView.addArrangedSubview(previewLabel) + contentView.addSubview(titleLabelStackView) contentView.addSubview(dateAndPreviewStackView) self.accessories = [.disclosureIndicator()] } - private func configureAutoLayout() { + private func configureLayout() { NSLayoutConstraint.activate([ titleLabelStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5), titleLabelStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), From 5560262103e87ef0d6d947f679100666312a462c Mon Sep 17 00:00:00 2001 From: idinaloq Date: Wed, 30 Aug 2023 11:41:30 +0900 Subject: [PATCH 12/38] =?UTF-8?q?refactor:=20=EA=B0=95=EC=A0=9C=EC=96=B8?= =?UTF-8?q?=EB=9E=98=ED=95=91=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary/Controller/DiaryDetailViewController.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index f155dc80f..a3c42f28b 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -41,7 +41,11 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - navigationItem.title = DateFormatter().formatDate(diaryEntity!) + guard let diaryEntity = diaryEntity else { + return + } + + navigationItem.title = DateFormatter().formatDate(diaryEntity) } private func configureUI() { From 9be4a065498793cd313dfb514d32c936f09d0cbe Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Wed, 30 Aug 2023 12:15:45 +0900 Subject: [PATCH 13/38] =?UTF-8?q?refactor:=20scrollIndicatorInsets?= =?UTF-8?q?=EC=9D=84=20verticalScrollIndicatorInsets=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary/Controller/NewDiaryViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index a43cfb5b1..e68624e4b 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -57,11 +57,11 @@ final class NewDiaryViewController: UIViewController { keyboardFrame = view.convert(keyboardFrame, from: nil) contentInset.bottom = keyboardFrame.size.height textView.contentInset = contentInset - textView.scrollIndicatorInsets = textView.contentInset + textView.verticalScrollIndicatorInsets = textView.contentInset } @objc private func keyboardWillHide() { textView.contentInset = UIEdgeInsets.zero - textView.scrollIndicatorInsets = textView.contentInset + textView.verticalScrollIndicatorInsets = textView.contentInset } } From 5b1dfee8a69dbd6352cc9e90525159e1bbc6d5a1 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Wed, 30 Aug 2023 17:15:46 +0900 Subject: [PATCH 14/38] =?UTF-8?q?refactor:=20=EB=A6=AC=EB=B7=B0=EC=97=90?= =?UTF-8?q?=20=EB=94=B0=EB=9D=BC=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 16 +++++++++ .../DiaryDetailViewController.swift | 15 ++------ .../Controller/DiaryListViewController.swift | 34 ++++++++++++++----- Diary/Error/DecodingError.swift | 17 ++++++++++ Diary/Extension/Array+.swift | 12 +++++++ Diary/View/DiaryCollectionViewListCell.swift | 25 ++++++++++---- 6 files changed, 92 insertions(+), 27 deletions(-) create mode 100644 Diary/Error/DecodingError.swift create mode 100644 Diary/Extension/Array+.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 684e2765d..8aafcb4b9 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */; }; 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */; }; 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */; }; + 3BBC989B2A9F1C4C0047DE81 /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC989A2A9F1C4C0047DE81 /* DecodingError.swift */; }; + 3BBC989D2A9F2BFA0047DE81 /* Array+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryListViewController.swift */; }; @@ -26,6 +28,8 @@ 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryEntity.swift; sourceTree = ""; }; 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryDetailViewController.swift; sourceTree = ""; }; 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; + 3BBC989A2A9F1C4C0047DE81 /* DecodingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingError.swift; sourceTree = ""; }; + 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = ""; }; C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -90,10 +94,19 @@ isa = PBXGroup; children = ( 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */, + 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */, ); path = Extension; sourceTree = ""; }; + 3BBC989E2A9F2C6F0047DE81 /* Error */ = { + isa = PBXGroup; + children = ( + 3BBC989A2A9F1C4C0047DE81 /* DecodingError.swift */, + ); + path = Error; + sourceTree = ""; + }; C739AE18284DF28600741E8F = { isa = PBXGroup; children = ( @@ -114,6 +127,7 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( + 3BBC989E2A9F2C6F0047DE81 /* Error */, 3BBC98972A9E44820047DE81 /* Extension */, 3BBC98922A9D897B0047DE81 /* Model */, 3BBC988E2A9D683C0047DE81 /* View */, @@ -220,6 +234,7 @@ buildActionMask = 2147483647; files = ( C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */, + 3BBC989D2A9F2BFA0047DE81 /* Array+.swift in Sources */, C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */, @@ -228,6 +243,7 @@ 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */, 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, + 3BBC989B2A9F1C4C0047DE81 /* DecodingError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index a3c42f28b..ff948cca4 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -8,16 +8,11 @@ import UIKit final class DiaryDetailViewController: UIViewController { - private var diaryEntity: DiaryEntity? + private var diaryEntity: DiaryEntity private lazy var textView: UITextView = { let view: UITextView = UITextView() view.translatesAutoresizingMaskIntoConstraints = false - - guard let diaryEntity = diaryEntity else { - return view - } - view.text = diaryEntity.title + "\n\n" + diaryEntity.body return view @@ -31,8 +26,8 @@ final class DiaryDetailViewController: UIViewController { setUpKeyboardEvent() } - init(data: DiaryEntity) { - self.diaryEntity = data + init(diaryEntity: DiaryEntity) { + self.diaryEntity = diaryEntity super.init(nibName: nil, bundle: nil) } @@ -41,10 +36,6 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - guard let diaryEntity = diaryEntity else { - return - } - navigationItem.title = DateFormatter().formatDate(diaryEntity) } diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index ff1c5c8b8..4794bf7b3 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -21,20 +21,32 @@ final class DiaryListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - decodeData() + updateDiaryEntity() configureCollectionView() configureNavigation() configureUI() configureLayout() } - private func decodeData() { + private func decodeData() throws -> [DiaryEntity]? { guard let asset = NSDataAsset(name: "sample") else { - return + throw DecodingError.decodingFailure } let decodedData = try? JSONDecoder().decode([DiaryEntity].self, from: asset.data) diaryEntity = decodedData + + return decodedData + } + + private func updateDiaryEntity() { + do { + diaryEntity = try decodeData() + } catch DecodingError.decodingFailure { + print(DecodingError.decodingFailure.description) + } catch { + print(error) + } } private func configureCollectionView() { @@ -43,7 +55,7 @@ final class DiaryListViewController: UIViewController { } private func configureNavigation() { - let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(createNewDiary)) + let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(hitCreateNewDiaryButton)) self.navigationItem.rightBarButtonItem = addDiary navigationItem.title = "일기장" } @@ -63,7 +75,7 @@ final class DiaryListViewController: UIViewController { ]) } - @objc private func createNewDiary() { + @objc private func hitCreateNewDiaryButton() { let newDiaryViewController: NewDiaryViewController = NewDiaryViewController() navigationController?.pushViewController(newDiaryViewController, animated: true) } @@ -83,11 +95,11 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD return UICollectionViewCell() } - guard let diaryEntity = diaryEntity else { - return UICollectionViewCell() + guard let diaryIndex = diaryEntity?[index: indexPath.item] else { + return cell } - cell.configureLabel(diaryEntity[indexPath.item]) + cell.configureLabel(with: diaryIndex) return cell } @@ -97,8 +109,12 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD return } + guard let diaryIndex = diaryEntity[index: indexPath.item] else { + return + } + collectionView.deselectItem(at: indexPath, animated: true) - let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(data: diaryEntity[indexPath.item]) + let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(diaryEntity: diaryIndex) navigationController?.pushViewController(diaryDetailViewController, animated: true) } } diff --git a/Diary/Error/DecodingError.swift b/Diary/Error/DecodingError.swift new file mode 100644 index 000000000..dc54ce6be --- /dev/null +++ b/Diary/Error/DecodingError.swift @@ -0,0 +1,17 @@ +// +// DecodingError.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/30. +// + +enum DecodingError: Error { + case decodingFailure + + var description: String { + switch self { + case .decodingFailure: + return "디코딩 에러입니다." + } + } +} diff --git a/Diary/Extension/Array+.swift b/Diary/Extension/Array+.swift new file mode 100644 index 000000000..753b70626 --- /dev/null +++ b/Diary/Extension/Array+.swift @@ -0,0 +1,12 @@ +// +// Array+.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/30. +// + +extension Array { + subscript(index index: Int) -> Element? { + return self.indices ~= index ? self[index] : nil + } +} diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index f6352eb9f..9203bae53 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -7,8 +7,14 @@ import UIKit -final class DiaryCollectionViewListCell: UICollectionViewListCell { - static let identifier: String = "DiaryCollectionViewListCell" +protocol IdentifiableCell { + static var identifier: String { get } +} + +final class DiaryCollectionViewListCell: UICollectionViewListCell, IdentifiableCell { + static var identifier: String { + return String(describing: self) + } private let titleLabel: UILabel = { let label: UILabel = UILabel() @@ -91,9 +97,16 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell { ]) } - func configureLabel(_ data: DiaryEntity) { - titleLabel.text = data.title - dateLabel.text = DateFormatter().formatDate(data) - previewLabel.text = data.body + func configureLabel(with diary: DiaryEntity) { + titleLabel.text = diary.title + dateLabel.text = DateFormatter().formatDate(diary) + previewLabel.text = diary.body + } + + override func prepareForReuse() { + super.prepareForReuse() + titleLabel.text = "" + dateLabel.text = "" + previewLabel.text = "" } } From 7a4276bbe271212a08ffd3fcec7f8c1ba69301ac Mon Sep 17 00:00:00 2001 From: idinaloq Date: Thu, 31 Aug 2023 17:29:12 +0900 Subject: [PATCH 15/38] =?UTF-8?q?refactor:=20KeyboardManager=EB=A1=9C=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=EC=BD=94=EB=93=9C=20=EA=B0=84=EC=86=8C?= =?UTF-8?q?=ED=99=94,=20TimeZone=EC=9D=84=20current=EB=A1=9C=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=EC=98=A4=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 12 +++ .../xcshareddata/xcschemes/Diary.xcscheme | 77 +++++++++++++++++++ .../DiaryDetailViewController.swift | 26 +------ Diary/Controller/NewDiaryViewController.swift | 27 ++----- Diary/Extension/DateFormatter+.swift | 2 +- Diary/Manager/KeyboardManager.swift | 40 ++++++++++ 6 files changed, 139 insertions(+), 45 deletions(-) create mode 100644 Diary.xcodeproj/xcshareddata/xcschemes/Diary.xcscheme create mode 100644 Diary/Manager/KeyboardManager.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 8aafcb4b9..25fd3d0f7 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C739AE32284DF28600741E8F /* LaunchScreen.storyboard */; }; DC3EA1662A9CAE8400986F72 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */; }; DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */; }; + DC5B192E2AA07FAD0064550A /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -40,6 +41,7 @@ C739AE35284DF28600741E8F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDiaryViewController.swift; sourceTree = ""; }; + DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -127,6 +129,7 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( + DC5B192C2AA07FA10064550A /* Manager */, 3BBC989E2A9F2C6F0047DE81 /* Error */, 3BBC98972A9E44820047DE81 /* Extension */, 3BBC98922A9D897B0047DE81 /* Model */, @@ -139,6 +142,14 @@ path = Diary; sourceTree = ""; }; + DC5B192C2AA07FA10064550A /* Manager */ = { + isa = PBXGroup; + children = ( + DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */, + ); + path = Manager; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -241,6 +252,7 @@ 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, + DC5B192E2AA07FAD0064550A /* KeyboardManager.swift in Sources */, DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */, 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, 3BBC989B2A9F1C4C0047DE81 /* DecodingError.swift in Sources */, diff --git a/Diary.xcodeproj/xcshareddata/xcschemes/Diary.xcscheme b/Diary.xcodeproj/xcshareddata/xcschemes/Diary.xcscheme new file mode 100644 index 000000000..90943b4f2 --- /dev/null +++ b/Diary.xcodeproj/xcshareddata/xcschemes/Diary.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index ff948cca4..7aee18cda 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -9,6 +9,7 @@ import UIKit final class DiaryDetailViewController: UIViewController { private var diaryEntity: DiaryEntity + private var keyboardManager: KeyboardManager? private lazy var textView: UITextView = { let view: UITextView = UITextView() @@ -23,7 +24,7 @@ final class DiaryDetailViewController: UIViewController { configureNavigation() configureUI() configureLayout() - setUpKeyboardEvent() + setUpKeyboard() } init(diaryEntity: DiaryEntity) { @@ -54,26 +55,7 @@ final class DiaryDetailViewController: UIViewController { ]) } - private 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 private func keyboardWillShow(_ notification: Notification) { - guard let userInfo = notification.userInfo as NSDictionary?, - var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { - return - } - - var contentInset = textView.contentInset - keyboardFrame = view.convert(keyboardFrame, from: nil) - contentInset.bottom = keyboardFrame.size.height - textView.contentInset = contentInset - textView.scrollIndicatorInsets = textView.contentInset - } - - @objc private func keyboardWillHide() { - textView.contentInset = UIEdgeInsets.zero - textView.scrollIndicatorInsets = textView.contentInset + private func setUpKeyboard() { + keyboardManager = KeyboardManager(textView: textView) } } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index e68624e4b..24182c718 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -15,12 +15,14 @@ final class NewDiaryViewController: UIViewController { return view }() + private var keyboardManager: KeyboardManager? + override func viewDidLoad() { super.viewDidLoad() configureNavigation() configureUI() configureLayout() - setUpKeyboardEvent() + setUpKeyboard() } private func configureNavigation() { @@ -42,26 +44,7 @@ final class NewDiaryViewController: UIViewController { ]) } - private 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 private func keyboardWillShow(_ notification: Notification) { - guard let userInfo = notification.userInfo as NSDictionary?, - var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { - return - } - - var contentInset = textView.contentInset - keyboardFrame = view.convert(keyboardFrame, from: nil) - contentInset.bottom = keyboardFrame.size.height - textView.contentInset = contentInset - textView.verticalScrollIndicatorInsets = textView.contentInset - } - - @objc private func keyboardWillHide() { - textView.contentInset = UIEdgeInsets.zero - textView.verticalScrollIndicatorInsets = textView.contentInset + private func setUpKeyboard() { + keyboardManager = KeyboardManager(textView: textView) } } diff --git a/Diary/Extension/DateFormatter+.swift b/Diary/Extension/DateFormatter+.swift index 373e29935..2b70956c7 100644 --- a/Diary/Extension/DateFormatter+.swift +++ b/Diary/Extension/DateFormatter+.swift @@ -22,7 +22,7 @@ extension DateFormatter { let dateFormatter: DateFormatter = DateFormatter() let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) dateFormatter.locale = Locale(identifier: "ko_KR") - dateFormatter.timeZone = TimeZone(abbreviation: "KST") + dateFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) dateFormatter.dateStyle = .long return dateFormatter.string(from: date) diff --git a/Diary/Manager/KeyboardManager.swift b/Diary/Manager/KeyboardManager.swift new file mode 100644 index 000000000..44fbae70a --- /dev/null +++ b/Diary/Manager/KeyboardManager.swift @@ -0,0 +1,40 @@ +// +// KeyboardManager.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/31. +// + +import UIKit + +final class KeyboardManager { + private let textView: UITextView + + init(textView: UITextView) { + self.textView = textView + setUpKeyboardEvent() + } + + private 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 private func keyboardWillShow(_ notification: Notification) { + guard let userInfo = notification.userInfo as NSDictionary?, + var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { + return + } + + keyboardFrame = textView.convert(keyboardFrame, from: nil) + var contentInset = textView.contentInset + contentInset.bottom = keyboardFrame.size.height + textView.contentInset = contentInset + textView.verticalScrollIndicatorInsets = textView.contentInset + } + + @objc private func keyboardWillHide() { + textView.contentInset = UIEdgeInsets.zero + textView.verticalScrollIndicatorInsets = textView.contentInset + } +} From 147826807253e49f7144a64989487f238755eae9 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Thu, 31 Aug 2023 17:56:05 +0900 Subject: [PATCH 16/38] =?UTF-8?q?feat:=20LocaleIdentifier=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20formatDate()=EA=B0=80=20locale=20?= =?UTF-8?q?=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=EB=A1=9C=20=EB=B0=9B?= =?UTF-8?q?=EC=95=84=EC=98=A4=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 12 ++++++++++++ .../DiaryDetailViewController.swift | 2 +- Diary/Enum/LocaleIdentifier.swift | 19 +++++++++++++++++++ Diary/Extension/DateFormatter+.swift | 8 ++++---- Diary/View/DiaryCollectionViewListCell.swift | 2 +- 5 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 Diary/Enum/LocaleIdentifier.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 25fd3d0f7..50c746e82 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ DC3EA1662A9CAE8400986F72 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */; }; DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */; }; DC5B192E2AA07FAD0064550A /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */; }; + DC5B19302AA08AE70064550A /* LocaleIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B192F2AA08AE70064550A /* LocaleIdentifier.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -42,6 +43,7 @@ DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDiaryViewController.swift; sourceTree = ""; }; DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; + DC5B192F2AA08AE70064550A /* LocaleIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocaleIdentifier.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -129,6 +131,7 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( + DC5B19312AA08AEB0064550A /* Enum */, DC5B192C2AA07FA10064550A /* Manager */, 3BBC989E2A9F2C6F0047DE81 /* Error */, 3BBC98972A9E44820047DE81 /* Extension */, @@ -150,6 +153,14 @@ path = Manager; sourceTree = ""; }; + DC5B19312AA08AEB0064550A /* Enum */ = { + isa = PBXGroup; + children = ( + DC5B192F2AA08AE70064550A /* LocaleIdentifier.swift */, + ); + path = Enum; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -251,6 +262,7 @@ 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */, 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, + DC5B19302AA08AE70064550A /* LocaleIdentifier.swift in Sources */, 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, DC5B192E2AA07FAD0064550A /* KeyboardManager.swift in Sources */, DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */, diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 7aee18cda..cb8be7c10 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -37,7 +37,7 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - navigationItem.title = DateFormatter().formatDate(diaryEntity) + navigationItem.title = DateFormatter().formatDate(diaryEntity, locale: .KOR) } private func configureUI() { diff --git a/Diary/Enum/LocaleIdentifier.swift b/Diary/Enum/LocaleIdentifier.swift new file mode 100644 index 000000000..7d2a2017b --- /dev/null +++ b/Diary/Enum/LocaleIdentifier.swift @@ -0,0 +1,19 @@ +// +// LocaleIdentifier.swift +// Diary +// +// Created by idinaloq, yetti on 2023/08/31. +// + +import Foundation + +enum LocaleIdentifier: CustomStringConvertible { + case KOR + + var description: String { + switch self { + case .KOR: + return "ko_KR" + } + } +} diff --git a/Diary/Extension/DateFormatter+.swift b/Diary/Extension/DateFormatter+.swift index 2b70956c7..3ff348727 100644 --- a/Diary/Extension/DateFormatter+.swift +++ b/Diary/Extension/DateFormatter+.swift @@ -11,17 +11,17 @@ extension DateFormatter { static var today: String { let dateFormatter: DateFormatter = DateFormatter() let date: Date = Date(timeIntervalSinceNow: 0) - dateFormatter.locale = Locale(identifier: "ko_KR") - dateFormatter.timeZone = TimeZone(abbreviation: "KST") + dateFormatter.locale = Locale(identifier: LocaleIdentifier.KOR.description) + dateFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) dateFormatter.dateStyle = .long return dateFormatter.string(from: date) } - func formatDate(_ data: DiaryEntity) -> String { + func formatDate(_ data: DiaryEntity, locale: LocaleIdentifier) -> String { let dateFormatter: DateFormatter = DateFormatter() let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) - dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.locale = Locale(identifier: locale.description) dateFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) dateFormatter.dateStyle = .long diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 9203bae53..fa2941408 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -99,7 +99,7 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell, IdentifiableC func configureLabel(with diary: DiaryEntity) { titleLabel.text = diary.title - dateLabel.text = DateFormatter().formatDate(diary) + dateLabel.text = DateFormatter().formatDate(diary, locale: .KOR) previewLabel.text = diary.body } From 8fa4d88d18c67870a8bfc2eec5934e6608b229b6 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Fri, 1 Sep 2023 12:14:30 +0900 Subject: [PATCH 17/38] =?UTF-8?q?refactor:=20step1(2=EC=B0=A8)=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=ED=95=98=EC=97=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 16 ++++++++++++++++ Diary/Controller/DiaryDetailViewController.swift | 10 +++++++--- Diary/Extension/CellIdentifiable+.swift | 12 ++++++++++++ Diary/Extension/DateFormatter+.swift | 11 +++++------ Diary/Protocol/CellIdentifiable.swift | 10 ++++++++++ Diary/View/DiaryCollectionViewListCell.swift | 12 ++---------- 6 files changed, 52 insertions(+), 19 deletions(-) create mode 100644 Diary/Extension/CellIdentifiable+.swift create mode 100644 Diary/Protocol/CellIdentifiable.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 50c746e82..a5ac07179 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 3B95E4922AA18B8A008BFD76 /* CellIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4912AA18B8A008BFD76 /* CellIdentifiable.swift */; }; + 3B95E4942AA18C44008BFD76 /* CellIdentifiable+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4932AA18C44008BFD76 /* CellIdentifiable+.swift */; }; 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */; }; 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */; }; 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */; }; @@ -26,6 +28,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 3B95E4912AA18B8A008BFD76 /* CellIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellIdentifiable.swift; sourceTree = ""; }; + 3B95E4932AA18C44008BFD76 /* CellIdentifiable+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CellIdentifiable+.swift"; sourceTree = ""; }; 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewListCell.swift; sourceTree = ""; }; 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryEntity.swift; sourceTree = ""; }; 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryDetailViewController.swift; sourceTree = ""; }; @@ -57,6 +61,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3B95E4902AA18B74008BFD76 /* Protocol */ = { + isa = PBXGroup; + children = ( + 3B95E4912AA18B8A008BFD76 /* CellIdentifiable.swift */, + ); + path = Protocol; + sourceTree = ""; + }; 3BBC988C2A9D67EC0047DE81 /* Resource */ = { isa = PBXGroup; children = ( @@ -97,6 +109,7 @@ 3BBC98972A9E44820047DE81 /* Extension */ = { isa = PBXGroup; children = ( + 3B95E4932AA18C44008BFD76 /* CellIdentifiable+.swift */, 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */, 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */, ); @@ -131,6 +144,7 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( + 3B95E4902AA18B74008BFD76 /* Protocol */, DC5B19312AA08AEB0064550A /* Enum */, DC5B192C2AA07FA10064550A /* Manager */, 3BBC989E2A9F2C6F0047DE81 /* Error */, @@ -257,6 +271,7 @@ files = ( C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */, 3BBC989D2A9F2BFA0047DE81 /* Array+.swift in Sources */, + 3B95E4942AA18C44008BFD76 /* CellIdentifiable+.swift in Sources */, C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */, @@ -266,6 +281,7 @@ 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, DC5B192E2AA07FAD0064550A /* KeyboardManager.swift in Sources */, DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */, + 3B95E4922AA18B8A008BFD76 /* CellIdentifiable.swift in Sources */, 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, 3BBC989B2A9F1C4C0047DE81 /* DecodingError.swift in Sources */, ); diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index cb8be7c10..ceb036868 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -11,10 +11,9 @@ final class DiaryDetailViewController: UIViewController { private var diaryEntity: DiaryEntity private var keyboardManager: KeyboardManager? - private lazy var textView: UITextView = { + private let textView: UITextView = { let view: UITextView = UITextView() view.translatesAutoresizingMaskIntoConstraints = false - view.text = diaryEntity.title + "\n\n" + diaryEntity.body return view }() @@ -23,6 +22,7 @@ final class DiaryDetailViewController: UIViewController { super.viewDidLoad() configureNavigation() configureUI() + configureTextView() configureLayout() setUpKeyboard() } @@ -37,7 +37,7 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - navigationItem.title = DateFormatter().formatDate(diaryEntity, locale: .KOR) + navigationItem.title = DateFormatter.formatDate(diaryEntity, locale: .KOR, formatter: DateFormatter()) } private func configureUI() { @@ -45,6 +45,10 @@ final class DiaryDetailViewController: UIViewController { view.backgroundColor = .systemBackground } + private func configureTextView() { + textView.text = diaryEntity.title + "\n\n" + diaryEntity.body + } + private func configureLayout() { let safeArea = view.safeAreaLayoutGuide NSLayoutConstraint.activate([ diff --git a/Diary/Extension/CellIdentifiable+.swift b/Diary/Extension/CellIdentifiable+.swift new file mode 100644 index 000000000..1d4a59a09 --- /dev/null +++ b/Diary/Extension/CellIdentifiable+.swift @@ -0,0 +1,12 @@ +// +// CellIdentifiable+.swift +// Diary +// +// Created by idinaloq, yetti on 2023/09/01. +// + +extension CellIdentifiable { + static var identifier: String { + return String(describing: self) + } +} diff --git a/Diary/Extension/DateFormatter+.swift b/Diary/Extension/DateFormatter+.swift index 3ff348727..8d9fe57fb 100644 --- a/Diary/Extension/DateFormatter+.swift +++ b/Diary/Extension/DateFormatter+.swift @@ -18,13 +18,12 @@ extension DateFormatter { return dateFormatter.string(from: date) } - func formatDate(_ data: DiaryEntity, locale: LocaleIdentifier) -> String { - let dateFormatter: DateFormatter = DateFormatter() + static func formatDate(_ data: DiaryEntity, locale: LocaleIdentifier, formatter: DateFormatter) -> String { let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) - dateFormatter.locale = Locale(identifier: locale.description) - dateFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) - dateFormatter.dateStyle = .long + formatter.locale = Locale(identifier: locale.description) + formatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) + formatter.dateStyle = .long - return dateFormatter.string(from: date) + return formatter.string(from: date) } } diff --git a/Diary/Protocol/CellIdentifiable.swift b/Diary/Protocol/CellIdentifiable.swift new file mode 100644 index 000000000..14b8d9ada --- /dev/null +++ b/Diary/Protocol/CellIdentifiable.swift @@ -0,0 +1,10 @@ +// +// IdentifiableCell.swift +// Diary +// +// Created by idinaloq, yetti on 2023/09/01. +// + +protocol CellIdentifiable { + static var identifier: String { get } +} diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index fa2941408..23152ca5b 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -7,15 +7,7 @@ import UIKit -protocol IdentifiableCell { - static var identifier: String { get } -} - -final class DiaryCollectionViewListCell: UICollectionViewListCell, IdentifiableCell { - static var identifier: String { - return String(describing: self) - } - +final class DiaryCollectionViewListCell: UICollectionViewListCell, CellIdentifiable { private let titleLabel: UILabel = { let label: UILabel = UILabel() label.translatesAutoresizingMaskIntoConstraints = false @@ -99,7 +91,7 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell, IdentifiableC func configureLabel(with diary: DiaryEntity) { titleLabel.text = diary.title - dateLabel.text = DateFormatter().formatDate(diary, locale: .KOR) + dateLabel.text = DateFormatter.formatDate(diary, locale: .KOR, formatter: DateFormatter()) previewLabel.text = diary.body } From 76d48417fe7e44461be557490cc0999a17201e88 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Fri, 1 Sep 2023 12:20:29 +0900 Subject: [PATCH 18/38] =?UTF-8?q?refactor:=20hitCreateNewDiaryButton=20->?= =?UTF-8?q?=20createNewDiaryButtonTapped=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary/Controller/DiaryListViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 4794bf7b3..b44d70ffb 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -55,7 +55,7 @@ final class DiaryListViewController: UIViewController { } private func configureNavigation() { - let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(hitCreateNewDiaryButton)) + let addDiary: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "plus"), style: .done, target: self, action: #selector(createNewDiaryButtonTapped)) self.navigationItem.rightBarButtonItem = addDiary navigationItem.title = "일기장" } @@ -75,7 +75,7 @@ final class DiaryListViewController: UIViewController { ]) } - @objc private func hitCreateNewDiaryButton() { + @objc private func createNewDiaryButtonTapped() { let newDiaryViewController: NewDiaryViewController = NewDiaryViewController() navigationController?.pushViewController(newDiaryViewController, animated: true) } From b689f439ad951f70cb43622db8bcfa973940a1a5 Mon Sep 17 00:00:00 2001 From: Yetti <100982422+iOS-Yetti@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:39:17 +0900 Subject: [PATCH 19/38] =?UTF-8?q?docs:=20=EC=9D=BC=EA=B8=B0=EC=9E=A5=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=201=EC=A3=BC=EC=B0=A8=20?= =?UTF-8?q?=EB=A6=AC=EB=93=9C=EB=AF=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 230 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 497819d76..46e7d8b06 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,233 @@ -## iOS 커리어 스타터 캠프 +# 📓 일기장 +## 📝 소개 +> 일기를 생성하고 작성 후에 저장할 수 있는 앱입니다. -### 일기장 프로젝트 저장소 +**프로젝트 기간 : 23/08/28~23/09/15** -- 이 저장소를 자신의 저장소로 fork하여 프로젝트를 진행합니다 -- 자신의 브랜치에 PR을 보내는지 꼭 확인한 후 PR을 보냅니다 +
+## 📖 목차 +1. [팀원 소개](#1.) +2. [타임 라인](#2.) +3. [시각화 구조](#3.) +4. [실행 화면](#4. ) +5. [핵심 경험](#5.) +6. [트러블 슈팅](#6.) +7. [참고 자료](#7.) +8. [팀 회고](#8.) + +
+## 👨‍💻 팀원 소개 +||| +|:-:|:-:| +|[**Yetti**](https://github.com/iOS-Yetti)|[**idinaloq**](https://github.com/idinaloq)| + +
+## ⏰ 타임 라인 +|날짜|내용| +|:--:|--| +|2023.08.28.|메인 스토리보드 삭제
SceneDelegate에 rootViewController 추가
SwiftLint적용| +|2023.08.29.|SwiftLint설정 변경
DiaryListViewController구현
DiaryCollectionViewListCell구현
DiaryEntity구현
DiaryDetailViewController생성| +|2023.08.30.|DateFormatter 기능확장
키보드 사용을 위한 setUpKeyboardEvent() 메서드 추가
NewDiaryViewController 구현
리팩토링
| +|2023.08.31.|KeyboardManager 클래스로 키보드 기능분리
LocaleIdentifier타입 생성
리팩토링| +|2023.09.01.|README작성| + +
+## 👀 시각화 구조 +### 1. File Tree + Diary + ├── Model + │   └── DiaryEntity.swift + ├── View + │ ├── LaunchScreen.storyboard + │ └── DiaryCollectionViewListCell.swift + ├── Controller + │   ├── DiaryListViewController.swift + │   ├── DiaryDetailViewController.swift + │   └── NewDiaryViewController.swift + ├── Enum + │   └── LocaleIdentifier.swift + ├── Error + │   └── DecodingError.swift + ├── Extension + │   ├── Array+.swift + │   └── DateFormatter+.swift + ├── Manager + │   └── KeyboardManager.swift + ├── Resource + │   ├── AppDelegate.swift + │   ├── SceneDelegate.swift + │   └── Assets.xcassets + ├── Info.plist + └── Diary.xcdatamodeld + +### 2. 클래스 다이어그램 +![일기장 UML](https://github.com/iOS-Yetti/ios-diary/assets/100982422/288cd6d5-b1e0-4153-9f04-d34949f0cba5) + +
+## 💻 실행화면 + +|실행화면(세로)| +|:---:| +|| + +|실행화면(가로)| +|:---:| +|| + +
+## 🧠 핵심경험 + +### 1️⃣ NotificationCenter를 활용한 키보드 설정 +- 텍스트를 수정할 때 키보드가 텍스트를 가리지 않도록 NotificationCenter의 [keyboardWillShowNotification](https://developer.apple.com/documentation/uikit/uiresponder/1621576-keyboardwillshownotification), [keyboardWillHideNotification](https://developer.apple.com/documentation/uikit/uiresponder/1621606-keyboardwillhidenotification)를 활용해 키보드가 나타나고, 사라질 때의 동작을 구현했습니다. +
+ 상세코드 + +```swift +import UIKit + +final class KeyboardManager { + private let textView: UITextView + + init(textView: UITextView) { + self.textView = textView + setUpKeyboardEvent() + } + + private 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 private func keyboardWillShow(_ notification: Notification) { + guard let userInfo = notification.userInfo as NSDictionary?, + var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { + return + } + + keyboardFrame = textView.convert(keyboardFrame, from: nil) + var contentInset = textView.contentInset + contentInset.bottom = keyboardFrame.size.height + textView.contentInset = contentInset + textView.verticalScrollIndicatorInsets = textView.contentInset + } + + @objc private func keyboardWillHide() { + textView.contentInset = UIEdgeInsets.zero + textView.verticalScrollIndicatorInsets = textView.contentInset + } +} + +``` + +
+ +### 2️⃣ DateFormatter +- [DateFormatter](https://developer.apple.com/documentation/foundation/dateformatter)를 활용해 현재 날짜, 변환 하려는 날짜를 Locale과 TimeZone에 맞는 형식으로 출력하도록 했습니다. + +
+ 상세코드 + +```swift +import Foundation + +extension DateFormatter { + static var today: String { + let dateFormatter: DateFormatter = DateFormatter() + let date: Date = Date(timeIntervalSinceNow: 0) + dateFormatter.locale = Locale(identifier: LocaleIdentifier.KOR.description) + dateFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) + dateFormatter.dateStyle = .long + + return dateFormatter.string(from: date) + } + + func formatDate(_ data: DiaryEntity, locale: LocaleIdentifier) -> String { + let dateFormatter: DateFormatter = DateFormatter() + let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) + dateFormatter.locale = Locale(identifier: locale.description) + dateFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) + dateFormatter.dateStyle = .long + + return dateFormatter.string(from: date) + } +} +``` + +
+ +### 3️⃣ subscript +- 배열에 범위를 벗어난 접근을 할 때 크래시가 발생하지 않도록 [subscript](https://developer.apple.com/documentation/foundation/data/3017410-subscript)를 사용해서 nil로 설정될 수 있도록 extension으로 Array의 기능을 확장했습니다. + +
+## 🧨 트러블 슈팅 + +### 1️⃣ out of bound +⚠️ **문제점**
+- collectionView 메서드에서 셀을 생성할 때, diaryEntity 배열에 indexPath.item으로 접근을 해서 데이터를 가져오고 있었습니다. 하지만 이렇게 되면 만약 diaryEntity 배열을 벗어난 indexPath로 접근을 하게되면 앱이 크래시가 날 수 있는 가능성이 있었습니다. + +**기존코드** +```swift +extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewDelegate { + ... + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DiaryCollectionViewListCell.identifier, for: indexPath) as? DiaryCollectionViewListCell else { + return UICollectionViewCell() + } + + guard let diaryEntity = diaryEntity else { + return UICollectionViewCell() + + cell.configureLabel(diaryEntity[indexPath.item]) + + return cell + } + ... +} +``` + +✅ **해결방법**
+- 배열에 잘못된 접근을 할 때(범위를 벗어난 접근) nil이 설정되도록 subscript를 사용해서 안전하게 배열에 접근할 수 있도록 array에 기능을 추가했고, diaryEntity가 nil일 때 빈 셀을 반환하는 부분도 그에 맞게 수정을 다음과 같이 진행했습니다. + +**현재코드** +```swift +extension Array { + subscript(index index: Int) -> Element? { + return self.indices ~= index ? self[index] : nil + } +} + +extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewDelegate { + ... + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DiaryCollectionViewListCell.identifier, for: indexPath) as? DiaryCollectionViewListCell else { + return UICollectionViewCell() + } + + guard let diaryIndex = diaryEntity?[index: indexPath.item] else { + return cell + } + + cell.configureLabel(with: diaryIndex) + + return cell + } + ... +} +``` + +
+## 📚 참고자료 + +- [🍎 Apple Docs: `DateFormatter`](https://developer.apple.com/documentation/foundation/dateformatter) +- [🍎 Apple Docs: `NotificationCenter`](https://developer.apple.com/documentation/foundation/notificationcenter) +- [🍎 Apple Docs: `keyboardWillShowNotification`](https://developer.apple.com/documentation/uikit/uiresponder/1621576-keyboardwillshownotification) +- [🍎 Apple Docs: `keyboardWillHideNotification`](https://developer.apple.com/documentation/uikit/uiresponder/1621606-keyboardwillhidenotification) +- [🍎 Apple Docs: `UITextView`](https://developer.apple.com/documentation/uikit/uitextview) +- [🌐 Blog: `subscript로 안전하게 배열 조회하기`](https://kkimin.tistory.com/86) +- [🌐 Blog: `키보드가 텍스트를 가리지 않도록 하기`](https://velog.io/@qudgh849/keyboard가-TextView를-가릴-때) +- [🌐 Blog: `identifier 재사용 프로토콜`](https://prod.velog.io/@yyyng/셀-재사용-프로토콜) + +
+## 👬 팀 회고 +프로젝트가 끝난 후 작성 예정입니다 (23.09.15) From a4cc0bf21abb9195b140c1efd7b84ba9b98e1da0 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Tue, 5 Sep 2023 14:16:04 +0900 Subject: [PATCH 20/38] =?UTF-8?q?feat:=20CoreData=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20textView=EC=97=90=20=ED=82=A4=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary+CoreDataClass.swift | 15 ++++++ Diary+CoreDataProperties.swift | 24 +++++++++ Diary.xcodeproj/project.pbxproj | 38 +++++++++++-- .../Diary.xcdatamodeld/.xccurrentversion | 0 .../Diary.xcdatamodel/contents | 8 +++ .../AppDelegate.swift | 22 ++------ Diary/Application/SceneDelegate.swift | 22 ++++++++ .../DiaryDetailViewController.swift | 2 + .../Controller/DiaryListViewController.swift | 7 ++- Diary/Controller/NewDiaryViewController.swift | 6 ++- .../Diary.xcdatamodel/contents | 4 -- Diary/Error/DecodingError.swift | 6 ++- Diary/Manager/CoreDataManager.swift | 9 ++++ Diary/{ => Resource}/Info.plist | 0 Diary/Resource/SceneDelegate.swift | 54 ------------------- Diary/View/DiaryCollectionViewListCell.swift | 1 - 16 files changed, 128 insertions(+), 90 deletions(-) create mode 100644 Diary+CoreDataClass.swift create mode 100644 Diary+CoreDataProperties.swift rename Diary/{ => Application Support}/Diary.xcdatamodeld/.xccurrentversion (100%) create mode 100644 Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents rename Diary/{Resource => Application}/AppDelegate.swift (72%) create mode 100644 Diary/Application/SceneDelegate.swift delete mode 100644 Diary/Diary.xcdatamodeld/Diary.xcdatamodel/contents create mode 100644 Diary/Manager/CoreDataManager.swift rename Diary/{ => Resource}/Info.plist (100%) delete mode 100644 Diary/Resource/SceneDelegate.swift diff --git a/Diary+CoreDataClass.swift b/Diary+CoreDataClass.swift new file mode 100644 index 000000000..a9fc11282 --- /dev/null +++ b/Diary+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// Diary+CoreDataClass.swift +// Diary +// +// Created by 예찬 on 2023/09/05. +// +// + +import Foundation +import CoreData + +@objc(Diary) +public class Diary: NSManagedObject { + +} diff --git a/Diary+CoreDataProperties.swift b/Diary+CoreDataProperties.swift new file mode 100644 index 000000000..f7879c07d --- /dev/null +++ b/Diary+CoreDataProperties.swift @@ -0,0 +1,24 @@ +// +// Diary+CoreDataProperties.swift +// Diary +// +// Created by 예찬 on 2023/09/05. +// +// + +import Foundation +import CoreData + +extension Diary { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Diary") + } + + @NSManaged public var createdAt: Int64 + @NSManaged public var title: String? + @NSManaged public var body: String? +} + +extension Diary: Identifiable { +} diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index a5ac07179..fb6274eb1 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -9,6 +9,9 @@ /* Begin PBXBuildFile section */ 3B95E4922AA18B8A008BFD76 /* CellIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4912AA18B8A008BFD76 /* CellIdentifiable.swift */; }; 3B95E4942AA18C44008BFD76 /* CellIdentifiable+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4932AA18C44008BFD76 /* CellIdentifiable+.swift */; }; + 3B95E4DE2AA5D24B008BFD76 /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4DD2AA5D24B008BFD76 /* CoreDataManager.swift */; }; + 3B95E4E52AA6E156008BFD76 /* Diary+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4E32AA6E155008BFD76 /* Diary+CoreDataClass.swift */; }; + 3B95E4E62AA6E156008BFD76 /* Diary+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4E42AA6E155008BFD76 /* Diary+CoreDataProperties.swift */; }; 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */; }; 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */; }; 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */; }; @@ -30,6 +33,9 @@ /* Begin PBXFileReference section */ 3B95E4912AA18B8A008BFD76 /* CellIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellIdentifiable.swift; sourceTree = ""; }; 3B95E4932AA18C44008BFD76 /* CellIdentifiable+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CellIdentifiable+.swift"; sourceTree = ""; }; + 3B95E4DD2AA5D24B008BFD76 /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; + 3B95E4E32AA6E155008BFD76 /* Diary+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diary+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; + 3B95E4E42AA6E155008BFD76 /* Diary+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diary+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewListCell.swift; sourceTree = ""; }; 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryEntity.swift; sourceTree = ""; }; 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryDetailViewController.swift; sourceTree = ""; }; @@ -69,11 +75,29 @@ path = Protocol; sourceTree = ""; }; - 3BBC988C2A9D67EC0047DE81 /* Resource */ = { + 3B95E4C12AA30D09008BFD76 /* Application */ = { isa = PBXGroup; children = ( C739AE24284DF28600741E8F /* AppDelegate.swift */, C739AE26284DF28600741E8F /* SceneDelegate.swift */, + ); + path = Application; + sourceTree = ""; + }; + 3B95E4C22AA30D4C008BFD76 /* Application Support */ = { + isa = PBXGroup; + children = ( + 3B95E4E32AA6E155008BFD76 /* Diary+CoreDataClass.swift */, + 3B95E4E42AA6E155008BFD76 /* Diary+CoreDataProperties.swift */, + C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */, + ); + path = "Application Support"; + sourceTree = ""; + }; + 3BBC988C2A9D67EC0047DE81 /* Resource */ = { + isa = PBXGroup; + children = ( + C739AE35284DF28600741E8F /* Info.plist */, C739AE30284DF28600741E8F /* Assets.xcassets */, ); path = Resource; @@ -144,6 +168,8 @@ C739AE23284DF28600741E8F /* Diary */ = { isa = PBXGroup; children = ( + 3B95E4C22AA30D4C008BFD76 /* Application Support */, + 3B95E4C12AA30D09008BFD76 /* Application */, 3B95E4902AA18B74008BFD76 /* Protocol */, DC5B19312AA08AEB0064550A /* Enum */, DC5B192C2AA07FA10064550A /* Manager */, @@ -153,8 +179,6 @@ 3BBC988E2A9D683C0047DE81 /* View */, 3BBC988D2A9D68290047DE81 /* Controller */, 3BBC988C2A9D67EC0047DE81 /* Resource */, - C739AE35284DF28600741E8F /* Info.plist */, - C739AE2D284DF28600741E8F /* Diary.xcdatamodeld */, ); path = Diary; sourceTree = ""; @@ -163,6 +187,7 @@ isa = PBXGroup; children = ( DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */, + 3B95E4DD2AA5D24B008BFD76 /* CoreDataManager.swift */, ); path = Manager; sourceTree = ""; @@ -283,7 +308,10 @@ DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */, 3B95E4922AA18B8A008BFD76 /* CellIdentifiable.swift in Sources */, 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, + 3B95E4DE2AA5D24B008BFD76 /* CoreDataManager.swift in Sources */, + 3B95E4E62AA6E156008BFD76 /* Diary+CoreDataProperties.swift in Sources */, 3BBC989B2A9F1C4C0047DE81 /* DecodingError.swift in Sources */, + 3B95E4E52AA6E156008BFD76 /* Diary+CoreDataClass.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -425,7 +453,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Diary/Info.plist; + INFOPLIST_FILE = Diary/Resource/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -452,7 +480,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = Diary/Info.plist; + INFOPLIST_FILE = Diary/Resource/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; diff --git a/Diary/Diary.xcdatamodeld/.xccurrentversion b/Diary/Application Support/Diary.xcdatamodeld/.xccurrentversion similarity index 100% rename from Diary/Diary.xcdatamodeld/.xccurrentversion rename to Diary/Application Support/Diary.xcdatamodeld/.xccurrentversion diff --git a/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents b/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents new file mode 100644 index 000000000..832dbbfb5 --- /dev/null +++ b/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Diary/Resource/AppDelegate.swift b/Diary/Application/AppDelegate.swift similarity index 72% rename from Diary/Resource/AppDelegate.swift rename to Diary/Application/AppDelegate.swift index f9b5a8a4b..85e04de06 100644 --- a/Diary/Resource/AppDelegate.swift +++ b/Diary/Application/AppDelegate.swift @@ -17,20 +17,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // 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. - } - - // MARK: - Core Data stack - +// // MARK: - Core Data stack +// lazy var persistentContainer: NSPersistentContainer = { /* The persistent container for the application. This implementation @@ -43,7 +31,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - + /* Typical reasons for an error here include: * The parent directory does not exist, cannot be created, or disallows writing. @@ -58,7 +46,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { return container }() - // MARK: - Core Data Saving support +// // MARK: - Core Data Saving support func saveContext () { let context = persistentContainer.viewContext @@ -73,6 +61,4 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { } } } - } - diff --git a/Diary/Application/SceneDelegate.swift b/Diary/Application/SceneDelegate.swift new file mode 100644 index 000000000..5a0330793 --- /dev/null +++ b/Diary/Application/SceneDelegate.swift @@ -0,0 +1,22 @@ +// +// Diary - SceneDelegate.swift +// Created by yagom. +// Copyright © yagom. All rights reserved. +// + +import UIKit + +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: windowScene) + let mainViewController: UIViewController = DiaryListViewController() + + let navigationController = UINavigationController(rootViewController: mainViewController) + window?.rootViewController = navigationController + window?.makeKeyAndVisible() + } +} diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index ceb036868..772760e90 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -14,6 +14,8 @@ final class DiaryDetailViewController: UIViewController { private let textView: UITextView = { let view: UITextView = UITextView() view.translatesAutoresizingMaskIntoConstraints = false + view.keyboardDismissMode = .interactive + view.alwaysBounceVertical = true return view }() diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index b44d70ffb..ffd27058f 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -42,16 +42,15 @@ final class DiaryListViewController: UIViewController { private func updateDiaryEntity() { do { diaryEntity = try decodeData() - } catch DecodingError.decodingFailure { - print(DecodingError.decodingFailure.description) } catch { - print(error) + print(error.localizedDescription) } } private func configureCollectionView() { collectionView.dataSource = self collectionView.delegate = self + // diffableDatasource } private func configureNavigation() { @@ -99,7 +98,7 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD return cell } - cell.configureLabel(with: diaryIndex) + cell.configureLabel(with: diaryIndex) // diary return cell } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index 24182c718..d3c191e65 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -8,15 +8,17 @@ import UIKit final class NewDiaryViewController: UIViewController { + private var keyboardManager: KeyboardManager? + private let textView: UITextView = { let view: UITextView = UITextView() view.translatesAutoresizingMaskIntoConstraints = false + view.keyboardDismissMode = .interactive + view.alwaysBounceVertical = true return view }() - private var keyboardManager: KeyboardManager? - override func viewDidLoad() { super.viewDidLoad() configureNavigation() diff --git a/Diary/Diary.xcdatamodeld/Diary.xcdatamodel/contents b/Diary/Diary.xcdatamodeld/Diary.xcdatamodel/contents deleted file mode 100644 index 50d2514e8..000000000 --- a/Diary/Diary.xcdatamodeld/Diary.xcdatamodel/contents +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Diary/Error/DecodingError.swift b/Diary/Error/DecodingError.swift index dc54ce6be..88823fb5d 100644 --- a/Diary/Error/DecodingError.swift +++ b/Diary/Error/DecodingError.swift @@ -5,10 +5,12 @@ // Created by idinaloq, yetti on 2023/08/30. // -enum DecodingError: Error { +import Foundation + +enum DecodingError: LocalizedError { case decodingFailure - var description: String { + var errorDescription: String? { switch self { case .decodingFailure: return "디코딩 에러입니다." diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift new file mode 100644 index 000000000..cfd7e2f89 --- /dev/null +++ b/Diary/Manager/CoreDataManager.swift @@ -0,0 +1,9 @@ +// +// CoreDataManager.swift +// Diary +// +// Created by 예찬 on 2023/09/04. +// + +import UIKit +import CoreData diff --git a/Diary/Info.plist b/Diary/Resource/Info.plist similarity index 100% rename from Diary/Info.plist rename to Diary/Resource/Info.plist diff --git a/Diary/Resource/SceneDelegate.swift b/Diary/Resource/SceneDelegate.swift deleted file mode 100644 index 6195830bc..000000000 --- a/Diary/Resource/SceneDelegate.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Diary - SceneDelegate.swift -// Created by yagom. -// Copyright © yagom. All rights reserved. -// - -import UIKit - -final class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let windowScene = (scene as? UIWindowScene) else { return } - window = UIWindow(windowScene: windowScene) - let mainViewController: UIViewController = DiaryListViewController() - - let navigationController = UINavigationController(rootViewController: mainViewController) - window?.rootViewController = navigationController - window?.makeKeyAndVisible() - } - - 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. - - // Save changes in the application's managed object context when the application transitions to the background. - (UIApplication.shared.delegate as? AppDelegate)?.saveContext() - } -} - diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 23152ca5b..77212f298 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -31,7 +31,6 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell, CellIdentifia let label: UILabel = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 - label.textAlignment = .left label.font = UIFont.systemFont(ofSize: 12) return label From 4d7d292d72b2fc1273dffb946e0edf7d9d4f40b0 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Tue, 5 Sep 2023 16:51:46 +0900 Subject: [PATCH 21/38] =?UTF-8?q?test:=20CoreData=20Test=EC=9A=A9=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swiftlint.yml | 7 +- Diary+CoreDataProperties.swift | 2 +- .../Diary.xcdatamodel/contents | 6 +- Diary/Application/AppDelegate.swift | 2 +- .../DiaryDetailViewController.swift | 3 +- .../Controller/DiaryListViewController.swift | 46 ++++++----- Diary/Controller/NewDiaryViewController.swift | 56 +++++++++++++- Diary/Extension/DateFormatter+.swift | 16 ++-- Diary/Manager/CoreDataManager.swift | 8 ++ Diary/Model/DiaryEntity.swift | 9 +-- .../sample.dataset/Contents.json | 13 ---- .../sample.dataset/sample.json | 77 ------------------- Diary/View/DiaryCollectionViewListCell.swift | 8 +- 13 files changed, 111 insertions(+), 142 deletions(-) delete mode 100644 Diary/Resource/Assets.xcassets/sample.dataset/Contents.json delete mode 100644 Diary/Resource/Assets.xcassets/sample.dataset/sample.json diff --git a/.swiftlint.yml b/.swiftlint.yml index f37688072..ad9650c5d 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,9 +1,10 @@ disabled_rules: - trailing_whitespace - line_length - + - force_cast + excluded: - - Diary/Resource/AppDelegate.swift - - Diary/Resource/SceneDelegate.swift + - Diary/Application/AppDelegate.swift + - Diary/Application/SceneDelegate.swift diff --git a/Diary+CoreDataProperties.swift b/Diary+CoreDataProperties.swift index f7879c07d..619a14611 100644 --- a/Diary+CoreDataProperties.swift +++ b/Diary+CoreDataProperties.swift @@ -15,7 +15,7 @@ extension Diary { return NSFetchRequest(entityName: "Diary") } - @NSManaged public var createdAt: Int64 + @NSManaged public var createdAt: String? @NSManaged public var title: String? @NSManaged public var body: String? } diff --git a/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents b/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents index 832dbbfb5..c82b9228b 100644 --- a/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents +++ b/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents @@ -1,8 +1,8 @@ - + - - + + \ No newline at end of file diff --git a/Diary/Application/AppDelegate.swift b/Diary/Application/AppDelegate.swift index 85e04de06..b496ac0f6 100644 --- a/Diary/Application/AppDelegate.swift +++ b/Diary/Application/AppDelegate.swift @@ -48,7 +48,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // // MARK: - Core Data Saving support - func saveContext () { + func saveContext() { let context = persistentContainer.viewContext if context.hasChanges { do { diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 772760e90..c6e821ad4 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -39,7 +39,8 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - navigationItem.title = DateFormatter.formatDate(diaryEntity, locale: .KOR, formatter: DateFormatter()) +// navigationItem.title = DateFormatter.formatDate(diaryEntity, locale: .KOR, formatter: DateFormatter()) + navigationItem.title = "aaa" } private func configureUI() { diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index ffd27058f..5583538e1 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -5,9 +5,11 @@ // import UIKit +import CoreData final class DiaryListViewController: UIViewController { private var diaryEntity: [DiaryEntity]? + var context = CoreDataManager.shared.context private let collectionView: UICollectionView = { let configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) @@ -21,32 +23,34 @@ final class DiaryListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - updateDiaryEntity() configureCollectionView() configureNavigation() configureUI() configureLayout() + print("viewDidLoad") + } - private func decodeData() throws -> [DiaryEntity]? { - guard let asset = NSDataAsset(name: "sample") else { - throw DecodingError.decodingFailure - } - - let decodedData = try? JSONDecoder().decode([DiaryEntity].self, from: asset.data) - diaryEntity = decodedData + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + print("viewWillAppear") +// let context = (UIApplication.shared.delegate as? AppDelegate)!.persistentContainer.viewContext - return decodedData - } - - private func updateDiaryEntity() { do { - diaryEntity = try decodeData() + let data = try context.fetch(Diary.fetchRequest()) + if let diaries = data as? [Diary] { + for diary in diaries { + print(diary.title) + print(diary.body) + print(diary.createdAt) + } + } + } catch { - print(error.localizedDescription) + print(error) } } - + private func configureCollectionView() { collectionView.dataSource = self collectionView.delegate = self @@ -83,7 +87,7 @@ final class DiaryListViewController: UIViewController { extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { guard let diaryEntity = diaryEntity else { - return 0 + return 5 } return diaryEntity.count @@ -94,11 +98,11 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD return UICollectionViewCell() } - guard let diaryIndex = diaryEntity?[index: indexPath.item] else { - return cell - } - - cell.configureLabel(with: diaryIndex) // diary +// guard let diaryIndex = diaryEntity?[index: indexPath.item] else { +// return cell +// } +// +// cell.configureLabel(with: ) // diary return cell } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index d3c191e65..d6670cdb6 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -6,9 +6,12 @@ // import UIKit +import CoreData final class NewDiaryViewController: UIViewController { private var keyboardManager: KeyboardManager? +// let context = (UIApplication.shared.delegate as? AppDelegate)!.persistentContainer.viewContext + var context = CoreDataManager.shared.context private let textView: UITextView = { let view: UITextView = UITextView() @@ -24,11 +27,34 @@ final class NewDiaryViewController: UIViewController { configureNavigation() configureUI() configureLayout() - setUpKeyboard() + setUpKeyboardEvent() + + guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: context) else { + return + } + + let object = NSManagedObject(entity: entity, insertInto: context) + object.setValue("a", forKey: "title") + object.setValue("b", forKey: "body") + object.setValue("c", forKey: "createdAt") + + do { + try context.save() + print("success") + } catch { + print(error) + } + do { + let data = try context.fetch(Diary.fetchRequest()) + print(data) + } catch { + print(error) + } + } - + private func configureNavigation() { - navigationItem.title = DateFormatter.today + navigationItem.title = "DateFormatter.today" } private func configureUI() { @@ -49,4 +75,28 @@ final class NewDiaryViewController: UIViewController { private func setUpKeyboard() { keyboardManager = KeyboardManager(textView: textView) } + + private 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 private func keyboardWillShow(_ notification: Notification) { + guard let userInfo = notification.userInfo as NSDictionary?, + var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { + return + } + + keyboardFrame = textView.convert(keyboardFrame, from: nil) + var contentInset = textView.contentInset + contentInset.bottom = keyboardFrame.size.height + textView.contentInset = contentInset + textView.verticalScrollIndicatorInsets = textView.contentInset + } + + @objc private func keyboardWillHide() { + textView.contentInset = UIEdgeInsets.zero + textView.verticalScrollIndicatorInsets = textView.contentInset + + } } diff --git a/Diary/Extension/DateFormatter+.swift b/Diary/Extension/DateFormatter+.swift index 8d9fe57fb..337f23de2 100644 --- a/Diary/Extension/DateFormatter+.swift +++ b/Diary/Extension/DateFormatter+.swift @@ -18,12 +18,12 @@ extension DateFormatter { return dateFormatter.string(from: date) } - static func formatDate(_ data: DiaryEntity, locale: LocaleIdentifier, formatter: DateFormatter) -> String { - let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) - formatter.locale = Locale(identifier: locale.description) - formatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) - formatter.dateStyle = .long - - return formatter.string(from: date) - } +// static func formatDate(_ data: DiaryEntity, locale: LocaleIdentifier, formatter: DateFormatter) -> String { +// let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) +// formatter.locale = Locale(identifier: locale.description) +// formatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) +// formatter.dateStyle = .long +// +// return formatter.string(from: date) +// } } diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index cfd7e2f89..a994fa7b9 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -7,3 +7,11 @@ import UIKit import CoreData + +class CoreDataManager { + static let shared = CoreDataManager() + + let context = (UIApplication.shared.delegate as? AppDelegate)!.persistentContainer.viewContext + + private init() {} +} diff --git a/Diary/Model/DiaryEntity.swift b/Diary/Model/DiaryEntity.swift index e851c39c3..6e9a05d54 100644 --- a/Diary/Model/DiaryEntity.swift +++ b/Diary/Model/DiaryEntity.swift @@ -5,12 +5,7 @@ // Created by idinaloq, yetti on 2023/08/29. // -struct DiaryEntity: Decodable { +struct DiaryEntity { let title, body: String - let createdAt: Int - - enum CodingKeys: String, CodingKey { - case title, body - case createdAt = "created_at" - } + let createdAt: String } diff --git a/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json b/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json deleted file mode 100644 index 86c8c9c95..000000000 --- a/Diary/Resource/Assets.xcassets/sample.dataset/Contents.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "data" : [ - { - "filename" : "sample.json", - "idiom" : "universal", - "universal-type-identifier" : "public.json" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Diary/Resource/Assets.xcassets/sample.dataset/sample.json b/Diary/Resource/Assets.xcassets/sample.dataset/sample.json deleted file mode 100644 index 6746d8848..000000000 --- a/Diary/Resource/Assets.xcassets/sample.dataset/sample.json +++ /dev/null @@ -1,77 +0,0 @@ -[ - { - "title": "똘기떵이호치새초미자축인묘", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "드라고요롱이마초미미진사오미", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "몽키키키강달찡찡신유술해", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "네번째", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "승리는 우리의 것", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "호롤ㄹ로롤롤로로로롤롤로롤롤ㄹ롤롤 나방이 홓ㄹ로롤롤ㄹ로로로ㅗ롤로롤", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "우주 외계인 그는 무서운 암흑대왕", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "하나면 하나지 둘이겠느냐~", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "더 내려가봐유?", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - }, - { - "title": "이것은 리스트의 아이템입니다. 쪼매 제목이 길쥬? 그렇습니다. 그래도 뭐... 해야죠 뭐", - "body": "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine.\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath\n\nI am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now.\n\nWhen, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath", - "created_at": 1608651333 - } -] \ No newline at end of file diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 77212f298..0b052d291 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -88,10 +88,10 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell, CellIdentifia ]) } - func configureLabel(with diary: DiaryEntity) { - titleLabel.text = diary.title - dateLabel.text = DateFormatter.formatDate(diary, locale: .KOR, formatter: DateFormatter()) - previewLabel.text = diary.body + func configureLabel(with diary: String) { + titleLabel.text = diary +// dateLabel.text = DateFormatter.formatDate(diary, locale: .KOR, formatter: DateFormatter()) +// previewLabel.text = diary.body } override func prepareForReuse() { From c9efa17a75a66ad2222324690f29a5b3e1cca7e0 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Wed, 6 Sep 2023 09:22:52 +0900 Subject: [PATCH 22/38] =?UTF-8?q?feat:=20CoreData=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EA=B3=BC=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary+CoreDataClass.swift | 1 - Diary/Application/AppDelegate.swift | 35 ++------- .../Controller/DiaryListViewController.swift | 47 +++++------- Diary/Controller/NewDiaryViewController.swift | 75 +++++++------------ Diary/Manager/CoreDataManager.swift | 10 ++- Diary/View/DiaryCollectionViewListCell.swift | 8 +- 6 files changed, 62 insertions(+), 114 deletions(-) diff --git a/Diary+CoreDataClass.swift b/Diary+CoreDataClass.swift index a9fc11282..9af415271 100644 --- a/Diary+CoreDataClass.swift +++ b/Diary+CoreDataClass.swift @@ -11,5 +11,4 @@ import CoreData @objc(Diary) public class Diary: NSManagedObject { - } diff --git a/Diary/Application/AppDelegate.swift b/Diary/Application/AppDelegate.swift index b496ac0f6..cad8b7a14 100644 --- a/Diary/Application/AppDelegate.swift +++ b/Diary/Application/AppDelegate.swift @@ -14,40 +14,21 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // Override point for customization after application launch. return true } - - // MARK: UISceneSession Lifecycle - -// // MARK: - Core Data stack -// + + // MARK: - Core Data stack + lazy var persistentContainer: NSPersistentContainer = { - /* - The persistent container for the application. This implementation - creates and returns a container, having loaded the store for the - application to it. This property is optional since there are legitimate - error conditions that could cause the creation of the store to fail. - */ let container = NSPersistentContainer(name: "Diary") - container.loadPersistentStores(completionHandler: { (storeDescription, error) in + container.loadPersistentStores { storeDescription, error in if let error = error as NSError? { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - - /* - Typical reasons for an error here include: - * The parent directory does not exist, cannot be created, or disallows writing. - * The persistent store is not accessible, due to permissions or data protection when the device is locked. - * The device is out of space. - * The store could not be migrated to the current model version. - Check the error message to determine what the actual problem was. - */ fatalError("Unresolved error \(error), \(error.userInfo)") } - }) + } return container }() - -// // MARK: - Core Data Saving support - + + // MARK: - Core Data Saving support + func saveContext() { let context = persistentContainer.viewContext if context.hasChanges { diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 5583538e1..39926e21d 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -9,7 +9,8 @@ import CoreData final class DiaryListViewController: UIViewController { private var diaryEntity: [DiaryEntity]? - var context = CoreDataManager.shared.context + var cellCount: Int = 0 + var diaries: [Diary] = [] private let collectionView: UICollectionView = { let configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) @@ -27,30 +28,20 @@ final class DiaryListViewController: UIViewController { configureNavigation() configureUI() configureLayout() - print("viewDidLoad") - } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - print("viewWillAppear") -// let context = (UIApplication.shared.delegate as? AppDelegate)!.persistentContainer.viewContext - + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) do { - let data = try context.fetch(Diary.fetchRequest()) - if let diaries = data as? [Diary] { - for diary in diaries { - print(diary.title) - print(diary.body) - print(diary.createdAt) - } - } - + let data = try CoreDataManager.shared.context.fetch(Diary.fetchRequest()) + cellCount = data.count + self.diaries = data } catch { - print(error) + print(error.localizedDescription) } + collectionView.reloadData() } - + private func configureCollectionView() { collectionView.dataSource = self collectionView.delegate = self @@ -86,11 +77,7 @@ final class DiaryListViewController: UIViewController { extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - guard let diaryEntity = diaryEntity else { - return 5 - } - - return diaryEntity.count + return cellCount } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { @@ -98,12 +85,12 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD return UICollectionViewCell() } -// guard let diaryIndex = diaryEntity?[index: indexPath.item] else { -// return cell -// } -// -// cell.configureLabel(with: ) // diary + guard let diary = diaries[index: indexPath.item] else { + return cell + } + cell.configureLabel(with: diary) + return cell } @@ -111,7 +98,7 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD guard let diaryEntity = diaryEntity else { return } - + guard let diaryIndex = diaryEntity[index: indexPath.item] else { return } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index d6670cdb6..b48c3519b 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -10,8 +10,10 @@ import CoreData final class NewDiaryViewController: UIViewController { private var keyboardManager: KeyboardManager? -// let context = (UIApplication.shared.delegate as? AppDelegate)!.persistentContainer.viewContext - var context = CoreDataManager.shared.context + private var today: String = "" + private var tt: String = "" + private var bd: String = "" + private var ca: String = "" private let textView: UITextView = { let view: UITextView = UITextView() @@ -27,36 +29,15 @@ final class NewDiaryViewController: UIViewController { configureNavigation() configureUI() configureLayout() - setUpKeyboardEvent() - - guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: context) else { - return - } - - let object = NSManagedObject(entity: entity, insertInto: context) - object.setValue("a", forKey: "title") - object.setValue("b", forKey: "body") - object.setValue("c", forKey: "createdAt") - - do { - try context.save() - print("success") - } catch { - print(error) - } - do { - let data = try context.fetch(Diary.fetchRequest()) - print(data) - } catch { - print(error) - } - + setUpKeyboard() + textView.delegate = self } private func configureNavigation() { - navigationItem.title = "DateFormatter.today" + navigationItem.title = DateFormatter.today + today = DateFormatter.today } - + private func configureUI() { view.addSubview(textView) view.backgroundColor = .systemBackground @@ -75,28 +56,26 @@ final class NewDiaryViewController: UIViewController { private func setUpKeyboard() { keyboardManager = KeyboardManager(textView: textView) } - - private 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 private func keyboardWillShow(_ notification: Notification) { - guard let userInfo = notification.userInfo as NSDictionary?, - var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { +} + +extension NewDiaryViewController: UITextViewDelegate { + func textViewDidEndEditing(_ textView: UITextView) { + guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: CoreDataManager.shared.context) else { return } - keyboardFrame = textView.convert(keyboardFrame, from: nil) - var contentInset = textView.contentInset - contentInset.bottom = keyboardFrame.size.height - textView.contentInset = contentInset - textView.verticalScrollIndicatorInsets = textView.contentInset - } - - @objc private func keyboardWillHide() { - textView.contentInset = UIEdgeInsets.zero - textView.verticalScrollIndicatorInsets = textView.contentInset - + let object = NSManagedObject(entity: entity, insertInto: CoreDataManager.shared.context) + object.setValue("타이틀틀틀", forKey: "title") + object.setValue(textView.text, forKey: "body") + object.setValue(today, forKey: "createdAt") + tt = "타이틀틀틀" + bd = textView.text + ca = today + do { + try CoreDataManager.shared.context.save() + print("success") + } catch { + print(error.localizedDescription) + } } } diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index a994fa7b9..226bcbfbf 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -2,16 +2,18 @@ // CoreDataManager.swift // Diary // -// Created by 예찬 on 2023/09/04. +// Created by idinaloq, yetti on 2023/09/04. // import UIKit import CoreData -class CoreDataManager { - static let shared = CoreDataManager() +final class CoreDataManager { + static let shared: CoreDataManager = CoreDataManager() + + private init() {} let context = (UIApplication.shared.delegate as? AppDelegate)!.persistentContainer.viewContext - private init() {} + } diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index 0b052d291..bb0d4cae8 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -88,10 +88,10 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell, CellIdentifia ]) } - func configureLabel(with diary: String) { - titleLabel.text = diary -// dateLabel.text = DateFormatter.formatDate(diary, locale: .KOR, formatter: DateFormatter()) -// previewLabel.text = diary.body + func configureLabel(with diary: Diary) { + titleLabel.text = diary.title + dateLabel.text = diary.createdAt + previewLabel.text = diary.body } override func prepareForReuse() { From e841701b45b9847e4cc150ff472c75f383cf1649 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Wed, 6 Sep 2023 20:21:34 +0900 Subject: [PATCH 23/38] =?UTF-8?q?feat:=20DetailViewController=EC=97=90?= =?UTF-8?q?=EC=84=9C=20CoreData=EB=A5=BC=20=EB=B0=9B=EC=95=84=EC=99=80?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=A0=95=ED=95=98=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diary Entity가 UUID를 가지도록 model data 추가 --- Diary+CoreDataProperties.swift | 1 + Diary.xcodeproj/project.pbxproj | 10 +++-- .../Diary.xcdatamodel/contents | 1 + Diary/Application/AppDelegate.swift | 1 - Diary/Application/SceneDelegate.swift | 4 ++ .../DiaryDetailViewController.swift | 43 ++++++++++++++++--- .../Controller/DiaryListViewController.swift | 10 ++--- Diary/Controller/NewDiaryViewController.swift | 5 +++ Diary/Manager/CoreDataManager.swift | 13 ++++++ Diary/Manager/KeyboardManager.swift | 2 + Diary/Model/DiaryEntity.swift | 1 + Diary/View/DiaryCollectionViewListCell.swift | 2 +- 12 files changed, 76 insertions(+), 17 deletions(-) diff --git a/Diary+CoreDataProperties.swift b/Diary+CoreDataProperties.swift index 619a14611..b1bd562dc 100644 --- a/Diary+CoreDataProperties.swift +++ b/Diary+CoreDataProperties.swift @@ -18,6 +18,7 @@ extension Diary { @NSManaged public var createdAt: String? @NSManaged public var title: String? @NSManaged public var body: String? + @NSManaged public var identifier: String? } extension Diary: Identifiable { diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index fb6274eb1..6cfa70f5b 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -380,7 +380,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + INFOPLIST_KEY_LSBackgroundOnly = NO; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -435,7 +436,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + INFOPLIST_KEY_LSBackgroundOnly = NO; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -458,7 +460,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -485,7 +487,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents b/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents index c82b9228b..f7829d639 100644 --- a/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents +++ b/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/Diary/Application/AppDelegate.swift b/Diary/Application/AppDelegate.swift index cad8b7a14..aa166b6c1 100644 --- a/Diary/Application/AppDelegate.swift +++ b/Diary/Application/AppDelegate.swift @@ -9,7 +9,6 @@ import CoreData @main final class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true diff --git a/Diary/Application/SceneDelegate.swift b/Diary/Application/SceneDelegate.swift index 5a0330793..6f73428b3 100644 --- a/Diary/Application/SceneDelegate.swift +++ b/Diary/Application/SceneDelegate.swift @@ -19,4 +19,8 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.rootViewController = navigationController window?.makeKeyAndVisible() } + + func sceneDidEnterBackground(_ scene: UIScene) { + + } } diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index c6e821ad4..089aad4ae 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -6,10 +6,12 @@ // import UIKit +import CoreData final class DiaryDetailViewController: UIViewController { - private var diaryEntity: DiaryEntity + private var uuid: String private var keyboardManager: KeyboardManager? + var fetchRequest: NSFetchRequest private let textView: UITextView = { let view: UITextView = UITextView() @@ -27,10 +29,13 @@ final class DiaryDetailViewController: UIViewController { configureTextView() configureLayout() setUpKeyboard() + textView.delegate = self + } - init(diaryEntity: DiaryEntity) { - self.diaryEntity = diaryEntity + init(uuid: String) { + self.uuid = uuid + self.fetchRequest = CoreDataManager.shared.receivePredicateData(uuid: uuid) super.init(nibName: nil, bundle: nil) } @@ -39,8 +44,7 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { -// navigationItem.title = DateFormatter.formatDate(diaryEntity, locale: .KOR, formatter: DateFormatter()) - navigationItem.title = "aaa" + navigationItem.title = "aa" } private func configureUI() { @@ -49,7 +53,12 @@ final class DiaryDetailViewController: UIViewController { } private func configureTextView() { - textView.text = diaryEntity.title + "\n\n" + diaryEntity.body + do { + let data = try CoreDataManager.shared.context.fetch(fetchRequest) + textView.text = data.first?.body + } catch { + print(error) + } } private func configureLayout() { @@ -66,3 +75,25 @@ final class DiaryDetailViewController: UIViewController { keyboardManager = KeyboardManager(textView: textView) } } + +extension DiaryDetailViewController: UITextViewDelegate { + func textViewDidEndEditing(_ textView: UITextView) { + do { + let data = try CoreDataManager.shared.context.fetch(fetchRequest) + data.first?.body = textView.text + saveCoreData() + } catch { + print(error) + } + } + + func saveCoreData() { + do { + try CoreDataManager.shared.context.save() + print("success") + } catch { + print(error.localizedDescription) + } + } +} + diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 39926e21d..895a40ba8 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -15,6 +15,8 @@ final class DiaryListViewController: UIViewController { private let collectionView: UICollectionView = { let configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) let layout = UICollectionViewCompositionalLayout.list(using: configuration) +// configuration.leadingSwipeActionsConfigurationProvider = { } + let view = UICollectionView(frame: .zero, collectionViewLayout: layout) view.translatesAutoresizingMaskIntoConstraints = false view.register(DiaryCollectionViewListCell.self, forCellWithReuseIdentifier: DiaryCollectionViewListCell.identifier) @@ -95,16 +97,14 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let diaryEntity = diaryEntity else { - return - } - guard let diaryIndex = diaryEntity[index: indexPath.item] else { + guard let uuid = diaries[index: indexPath.item]?.identifier else { return } + print(uuid) collectionView.deselectItem(at: indexPath, animated: true) - let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(diaryEntity: diaryIndex) + let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(uuid: uuid) navigationController?.pushViewController(diaryDetailViewController, animated: true) } } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index b48c3519b..de1514671 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -68,9 +68,14 @@ extension NewDiaryViewController: UITextViewDelegate { object.setValue("타이틀틀틀", forKey: "title") object.setValue(textView.text, forKey: "body") object.setValue(today, forKey: "createdAt") + object.setValue(UUID().uuidString, forKey: "identifier") tt = "타이틀틀틀" bd = textView.text ca = today + saveCoreData() + } + + func saveCoreData() { do { try CoreDataManager.shared.context.save() print("success") diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index 226bcbfbf..a86f2e0b2 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -14,6 +14,19 @@ final class CoreDataManager { private init() {} let context = (UIApplication.shared.delegate as? AppDelegate)!.persistentContainer.viewContext + let fetchRequest: NSFetchRequest = Diary.fetchRequest() + func receivePredicateData(uuid: String) -> NSFetchRequest { + fetchRequest.predicate = NSPredicate(format: "identifier == %@", uuid) + + return fetchRequest + } + func receiveFetchData(fetchRequest: NSFetchRequest) { + do { + let data = try CoreDataManager.shared.context.fetch(fetchRequest) + } catch { + print(error.localizedDescription) + } + } } diff --git a/Diary/Manager/KeyboardManager.swift b/Diary/Manager/KeyboardManager.swift index 44fbae70a..bab5d0645 100644 --- a/Diary/Manager/KeyboardManager.swift +++ b/Diary/Manager/KeyboardManager.swift @@ -15,6 +15,8 @@ final class KeyboardManager { setUpKeyboardEvent() } + + private 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) diff --git a/Diary/Model/DiaryEntity.swift b/Diary/Model/DiaryEntity.swift index 6e9a05d54..d5c66554b 100644 --- a/Diary/Model/DiaryEntity.swift +++ b/Diary/Model/DiaryEntity.swift @@ -8,4 +8,5 @@ struct DiaryEntity { let title, body: String let createdAt: String + let identifier: Int } diff --git a/Diary/View/DiaryCollectionViewListCell.swift b/Diary/View/DiaryCollectionViewListCell.swift index bb0d4cae8..b0c5f0db6 100644 --- a/Diary/View/DiaryCollectionViewListCell.swift +++ b/Diary/View/DiaryCollectionViewListCell.swift @@ -32,7 +32,7 @@ final class DiaryCollectionViewListCell: UICollectionViewListCell, CellIdentifia label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 1 label.font = UIFont.systemFont(ofSize: 12) - + label.setContentHuggingPriority(.init(300), for: .horizontal) return label }() From 5e3dbb6e8971abd3fef65d22f8cc4d3640d0a28d Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Wed, 6 Sep 2023 21:20:48 +0900 Subject: [PATCH 24/38] =?UTF-8?q?refactor:=20CoreData=20CRUD=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary/Application/AppDelegate.swift | 28 ---------- .../DiaryDetailViewController.swift | 33 +++--------- .../Controller/DiaryListViewController.swift | 12 +---- Diary/Controller/NewDiaryViewController.swift | 32 +++-------- Diary/Manager/CoreDataManager.swift | 53 +++++++++++++++++-- Diary/Manager/KeyboardManager.swift | 2 - 6 files changed, 62 insertions(+), 98 deletions(-) diff --git a/Diary/Application/AppDelegate.swift b/Diary/Application/AppDelegate.swift index aa166b6c1..9c4e0ce5d 100644 --- a/Diary/Application/AppDelegate.swift +++ b/Diary/Application/AppDelegate.swift @@ -13,32 +13,4 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // Override point for customization after application launch. return true } - - // MARK: - Core Data stack - - lazy var persistentContainer: NSPersistentContainer = { - let container = NSPersistentContainer(name: "Diary") - container.loadPersistentStores { storeDescription, error in - if let error = error as NSError? { - fatalError("Unresolved error \(error), \(error.userInfo)") - } - } - return container - }() - - // MARK: - Core Data Saving support - - func saveContext() { - let context = persistentContainer.viewContext - if context.hasChanges { - do { - try context.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") - } - } - } } diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 089aad4ae..826818537 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -29,13 +29,11 @@ final class DiaryDetailViewController: UIViewController { configureTextView() configureLayout() setUpKeyboard() - textView.delegate = self - } init(uuid: String) { self.uuid = uuid - self.fetchRequest = CoreDataManager.shared.receivePredicateData(uuid: uuid) + self.fetchRequest = CoreDataManager.shared.receiveFetchRequest(for: uuid) super.init(nibName: nil, bundle: nil) } @@ -44,7 +42,7 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - navigationItem.title = "aa" + navigationItem.title = CoreDataManager.shared.fetchDiary(fetchRequest).first?.createdAt } private func configureUI() { @@ -53,12 +51,8 @@ final class DiaryDetailViewController: UIViewController { } private func configureTextView() { - do { - let data = try CoreDataManager.shared.context.fetch(fetchRequest) - textView.text = data.first?.body - } catch { - print(error) - } + textView.delegate = self + textView.text = CoreDataManager.shared.fetchDiary(fetchRequest).first?.body } private func configureLayout() { @@ -78,22 +72,7 @@ final class DiaryDetailViewController: UIViewController { extension DiaryDetailViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { - do { - let data = try CoreDataManager.shared.context.fetch(fetchRequest) - data.first?.body = textView.text - saveCoreData() - } catch { - print(error) - } - } - - func saveCoreData() { - do { - try CoreDataManager.shared.context.save() - print("success") - } catch { - print(error.localizedDescription) - } + CoreDataManager.shared.fetchDiary(fetchRequest).first?.body = textView.text + CoreDataManager.shared.saveContext() } } - diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 895a40ba8..a1f86c71c 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -8,8 +8,6 @@ import UIKit import CoreData final class DiaryListViewController: UIViewController { - private var diaryEntity: [DiaryEntity]? - var cellCount: Int = 0 var diaries: [Diary] = [] private let collectionView: UICollectionView = { @@ -34,13 +32,7 @@ final class DiaryListViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - do { - let data = try CoreDataManager.shared.context.fetch(Diary.fetchRequest()) - cellCount = data.count - self.diaries = data - } catch { - print(error.localizedDescription) - } + self.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) collectionView.reloadData() } @@ -79,7 +71,7 @@ final class DiaryListViewController: UIViewController { extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return cellCount + return diaries.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index de1514671..15535a272 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -11,9 +11,6 @@ import CoreData final class NewDiaryViewController: UIViewController { private var keyboardManager: KeyboardManager? private var today: String = "" - private var tt: String = "" - private var bd: String = "" - private var ca: String = "" private let textView: UITextView = { let view: UITextView = UITextView() @@ -29,8 +26,8 @@ final class NewDiaryViewController: UIViewController { configureNavigation() configureUI() configureLayout() + configureTextView() setUpKeyboard() - textView.delegate = self } private func configureNavigation() { @@ -53,6 +50,10 @@ final class NewDiaryViewController: UIViewController { ]) } + private func configureTextView() { + textView.delegate = self + } + private func setUpKeyboard() { keyboardManager = KeyboardManager(textView: textView) } @@ -60,27 +61,6 @@ final class NewDiaryViewController: UIViewController { extension NewDiaryViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { - guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: CoreDataManager.shared.context) else { - return - } - - let object = NSManagedObject(entity: entity, insertInto: CoreDataManager.shared.context) - object.setValue("타이틀틀틀", forKey: "title") - object.setValue(textView.text, forKey: "body") - object.setValue(today, forKey: "createdAt") - object.setValue(UUID().uuidString, forKey: "identifier") - tt = "타이틀틀틀" - bd = textView.text - ca = today - saveCoreData() - } - - func saveCoreData() { - do { - try CoreDataManager.shared.context.save() - print("success") - } catch { - print(error.localizedDescription) - } + CoreDataManager.shared.createDiary(textView) } } diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index a86f2e0b2..3cde071b4 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -13,20 +13,63 @@ final class CoreDataManager { private init() {} - let context = (UIApplication.shared.delegate as? AppDelegate)!.persistentContainer.viewContext - let fetchRequest: NSFetchRequest = Diary.fetchRequest() + var context: NSManagedObjectContext { + return persistentContainer.viewContext + } - func receivePredicateData(uuid: String) -> NSFetchRequest { + func receiveFetchRequest(for uuid: String) -> NSFetchRequest { + let fetchRequest: NSFetchRequest = Diary.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "identifier == %@", uuid) return fetchRequest } - func receiveFetchData(fetchRequest: NSFetchRequest) { + // create + func createDiary(_ textView: UITextView) { + guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: CoreDataManager.shared.context) else { + return + } + let object = NSManagedObject(entity: entity, insertInto: CoreDataManager.shared.context) + object.setValue("타이틀틀", forKey: "title") + object.setValue(textView.text, forKey: "body") + object.setValue(DateFormatter.today, forKey: "createdAt") + object.setValue(UUID().uuidString, forKey: "identifier") + saveContext() + } + // fetch + func fetchDiary(_ request: NSFetchRequest) -> [Diary] { do { - let data = try CoreDataManager.shared.context.fetch(fetchRequest) + let data = try context.fetch(request) + return data } catch { print(error.localizedDescription) } + return [] + } + + // update + // delete + + lazy var persistentContainer: NSPersistentContainer = { + let container = NSPersistentContainer(name: "Diary") + container.loadPersistentStores { _, error in + if let error = error as NSError? { + fatalError("Unresolved error \(error), \(error.userInfo)") + } + } + return container + }() + + func saveContext() { + let context = persistentContainer.viewContext + if context.hasChanges { + do { + try context.save() + } catch { + let nserror = error as NSError + fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + } + } } } diff --git a/Diary/Manager/KeyboardManager.swift b/Diary/Manager/KeyboardManager.swift index bab5d0645..44fbae70a 100644 --- a/Diary/Manager/KeyboardManager.swift +++ b/Diary/Manager/KeyboardManager.swift @@ -15,8 +15,6 @@ final class KeyboardManager { setUpKeyboardEvent() } - - private 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) From ae6c09252ab302e01d7f8aa1675d3c24bd5cdd30 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Thu, 7 Sep 2023 21:08:45 +0900 Subject: [PATCH 25/38] =?UTF-8?q?feat:=20swipe,=20CoreData=20Delete=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/DiaryListViewController.swift | 32 +++++++++++++++---- Diary/Manager/CoreDataManager.swift | 8 +++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index a1f86c71c..41d7a271e 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -10,10 +10,28 @@ import CoreData final class DiaryListViewController: UIViewController { var diaries: [Diary] = [] - private let collectionView: UICollectionView = { - let configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) + private lazy var collectionView: UICollectionView = { + var configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) + configuration.trailingSwipeActionsConfigurationProvider = { indexPath in + let delete = UIContextualAction(style: .destructive, title: "Delete") {[weak self] _, _, completionHandler in + guard let uuid = CoreDataManager.shared.fetchDiary(Diary.fetchRequest())[index: indexPath.item]?.identifier else { return } + + CoreDataManager.shared.deleteDiary(uuid) + self?.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) + self?.collectionView.reloadData() + + completionHandler(true) + } + + let share = UIContextualAction(style: .normal, title: "Share") {_, _, completionHandler in + + completionHandler(true) + } + + return UISwipeActionsConfiguration(actions: [delete, share]) + } + let layout = UICollectionViewCompositionalLayout.list(using: configuration) -// configuration.leadingSwipeActionsConfigurationProvider = { } let view = UICollectionView(frame: .zero, collectionViewLayout: layout) view.translatesAutoresizingMaskIntoConstraints = false @@ -28,6 +46,7 @@ final class DiaryListViewController: UIViewController { configureNavigation() configureUI() configureLayout() + } override func viewDidAppear(_ animated: Bool) { @@ -62,7 +81,7 @@ final class DiaryListViewController: UIViewController { collectionView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor) ]) } - + @objc private func createNewDiaryButtonTapped() { let newDiaryViewController: NewDiaryViewController = NewDiaryViewController() navigationController?.pushViewController(newDiaryViewController, animated: true) @@ -84,12 +103,12 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD } cell.configureLabel(with: diary) - + return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - + guard let uuid = diaries[index: indexPath.item]?.identifier else { return } @@ -99,4 +118,5 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(uuid: uuid) navigationController?.pushViewController(diaryDetailViewController, animated: true) } + } diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index 3cde071b4..61dafab9b 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -50,6 +50,14 @@ final class CoreDataManager { // update // delete + func deleteDiary(_ uuid: String) { +// let object = diary.first! + let fetchRequest = CoreDataManager.shared.receiveFetchRequest(for: uuid) + + let diary = CoreDataManager.shared.fetchDiary(fetchRequest) + persistentContainer.viewContext.delete(diary.first!) + saveContext() + } lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "Diary") From 24792bf85653ca651b6f6247e423c170048611bb Mon Sep 17 00:00:00 2001 From: Yetti <100982422+iOS-Yetti@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:08:38 +0900 Subject: [PATCH 26/38] =?UTF-8?q?docs:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=202=EC=A3=BC=EC=B0=A8=20README=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 247 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 138 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 46e7d8b06..bcf4cec45 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,21 @@ # 📓 일기장 -## 📝 소개 -> 일기를 생성하고 작성 후에 저장할 수 있는 앱입니다. +### 일기를 생성하고 작성 후에 저장 및 삭제할 수 있는 앱입니다. -**프로젝트 기간 : 23/08/28~23/09/15** +> **핵심 개념 및 경험** +> +> - **DateFormatter** +> - `locale` 프로퍼티를 이용한 지역화 +> - **CoreData** +> - `CoreData`모델을 통한 CRUD 기능 +> - (Create, Read(Retrieve), Update, Delete) +> - **UITextView** +> - `UITextView`에서 텍스트 편집 +> - **keyboardWillShowNotification / keyboardWillHideNotification** +> - 키보드가 나타나거나 사라질 때 `post`된 `Notification`을 `addObserver`를 통해 수신 +> - **subscript** +> - 배열의 범위를 벗어난 접근을 할 때 안전하게 접근할 수 있도록 `subscript`를 사용하여 `Array`의 기능 확장 + +**프로젝트 기간 : 23.08.28 ~ 23.09.15**
@@ -11,10 +24,9 @@ 2. [타임 라인](#2.) 3. [시각화 구조](#3.) 4. [실행 화면](#4. ) -5. [핵심 경험](#5.) -6. [트러블 슈팅](#6.) -7. [참고 자료](#7.) -8. [팀 회고](#8.) +5. [트러블 슈팅](#5.) +6. [참고 자료](#6.) +7. [팀 회고](#7.)
## 👨‍💻 팀원 소개 @@ -30,20 +42,25 @@ |2023.08.29.|SwiftLint설정 변경
DiaryListViewController구현
DiaryCollectionViewListCell구현
DiaryEntity구현
DiaryDetailViewController생성| |2023.08.30.|DateFormatter 기능확장
키보드 사용을 위한 setUpKeyboardEvent() 메서드 추가
NewDiaryViewController 구현
리팩토링
| |2023.08.31.|KeyboardManager 클래스로 키보드 기능분리
LocaleIdentifier타입 생성
리팩토링| -|2023.09.01.|README작성| +|2023.09.01.|README 작성| +|2023.09.04.|CoreData생성
textView키보드 기능추가
테스트용json제거| +|2023.09.05|CoreData 테스트용 코드 작성| +|2023.09.06|CoreData의 Create,Retieve,Update기능 구현
CoreData관련코드 리팩토링| +|2023.09.07|CoreData의 Delete기능 추가
swipe기능 구현| +|2023.09.08|README 작성|
## 👀 시각화 구조 ### 1. File Tree Diary - ├── Model - │   └── DiaryEntity.swift - ├── View - │ ├── LaunchScreen.storyboard - │ └── DiaryCollectionViewListCell.swift + ├── Application + │   ├── AppDelegate.swift + │   └── SceneDelegate.swift + ├── Application Support + │   └── Diary.xcdatamodeld  ├── Controller - │   ├── DiaryListViewController.swift │   ├── DiaryDetailViewController.swift + │   ├── DiaryListViewController.swift │   └── NewDiaryViewController.swift ├── Enum │   └── LocaleIdentifier.swift @@ -51,118 +68,44 @@ │   └── DecodingError.swift ├── Extension │   ├── Array+.swift + │   ├── CellIdentifiable+.swift │   └── DateFormatter+.swift ├── Manager + │   ├── CoreDataManager.swift │   └── KeyboardManager.swift + ├── Model + │   └── DiaryEntity.swift + ├── Protocol + │   └── CellIdentifiable.swift + ├── View + │ ├── Base.lproj + │ │   └── LaunchScreen.storyboard + │ └── DiaryCollectionViewListCell.swift ├── Resource - │   ├── AppDelegate.swift - │   ├── SceneDelegate.swift - │   └── Assets.xcassets - ├── Info.plist - └── Diary.xcdatamodeld + │   ├── Assets.xcassets + │   │   ├── AccentColor.colorset + │   │   ├── AppIcon.appiconset + └── └── Info.plist + ### 2. 클래스 다이어그램 -![일기장 UML](https://github.com/iOS-Yetti/ios-diary/assets/100982422/288cd6d5-b1e0-4153-9f04-d34949f0cba5) +![일기장 다이어그램](https://github.com/iOS-Yetti/ios-diary/assets/100982422/eb223cb6-a8ee-40ad-ba2d-ed0dcde88432)
## 💻 실행화면 |실행화면(세로)| |:---:| -|| +|| |실행화면(가로)| |:---:| ||
-## 🧠 핵심경험 - -### 1️⃣ NotificationCenter를 활용한 키보드 설정 -- 텍스트를 수정할 때 키보드가 텍스트를 가리지 않도록 NotificationCenter의 [keyboardWillShowNotification](https://developer.apple.com/documentation/uikit/uiresponder/1621576-keyboardwillshownotification), [keyboardWillHideNotification](https://developer.apple.com/documentation/uikit/uiresponder/1621606-keyboardwillhidenotification)를 활용해 키보드가 나타나고, 사라질 때의 동작을 구현했습니다. -
- 상세코드 - -```swift -import UIKit - -final class KeyboardManager { - private let textView: UITextView - - init(textView: UITextView) { - self.textView = textView - setUpKeyboardEvent() - } - - private 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 private func keyboardWillShow(_ notification: Notification) { - guard let userInfo = notification.userInfo as NSDictionary?, - var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { - return - } - - keyboardFrame = textView.convert(keyboardFrame, from: nil) - var contentInset = textView.contentInset - contentInset.bottom = keyboardFrame.size.height - textView.contentInset = contentInset - textView.verticalScrollIndicatorInsets = textView.contentInset - } - - @objc private func keyboardWillHide() { - textView.contentInset = UIEdgeInsets.zero - textView.verticalScrollIndicatorInsets = textView.contentInset - } -} - -``` - -
- -### 2️⃣ DateFormatter -- [DateFormatter](https://developer.apple.com/documentation/foundation/dateformatter)를 활용해 현재 날짜, 변환 하려는 날짜를 Locale과 TimeZone에 맞는 형식으로 출력하도록 했습니다. - -
- 상세코드 - -```swift -import Foundation - -extension DateFormatter { - static var today: String { - let dateFormatter: DateFormatter = DateFormatter() - let date: Date = Date(timeIntervalSinceNow: 0) - dateFormatter.locale = Locale(identifier: LocaleIdentifier.KOR.description) - dateFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) - dateFormatter.dateStyle = .long - - return dateFormatter.string(from: date) - } - - func formatDate(_ data: DiaryEntity, locale: LocaleIdentifier) -> String { - let dateFormatter: DateFormatter = DateFormatter() - let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) - dateFormatter.locale = Locale(identifier: locale.description) - dateFormatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) - dateFormatter.dateStyle = .long - - return dateFormatter.string(from: date) - } -} -``` - -
- -### 3️⃣ subscript -- 배열에 범위를 벗어난 접근을 할 때 크래시가 발생하지 않도록 [subscript](https://developer.apple.com/documentation/foundation/data/3017410-subscript)를 사용해서 nil로 설정될 수 있도록 extension으로 Array의 기능을 확장했습니다. - -
## 🧨 트러블 슈팅 -### 1️⃣ out of bound +### 1️⃣ out of range ⚠️ **문제점**
- collectionView 메서드에서 셀을 생성할 때, diaryEntity 배열에 indexPath.item으로 접근을 해서 데이터를 가져오고 있었습니다. 하지만 이렇게 되면 만약 diaryEntity 배열을 벗어난 indexPath로 접근을 하게되면 앱이 크래시가 날 수 있는 가능성이 있었습니다. @@ -216,18 +159,104 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD } ``` -
-## 📚 참고자료 +### 2️⃣ View의 LifeCycle +⚠️ **문제점**
+- `NavigationController`를 통해 다음 뷰로 이동하고 다시 이전 뷰 컨트롤러로 돌아올 때 일기장이 생성되거나 수정된 변경사항을 `cell`에 업데이트 하기 위해 `viewWillAppear()`메서드에 `collectionView.reloadData()` 메서드를 통해 셀이 다시 그려지도록 했습니다. +- 하지만 셀이 업데이트가 되지 않고, 한 번씩 업데이트 주기가 밀리는 현상이 있었습니다. +(다음 데이터가 들어와야 이전 데이터가 업데이트 되는 현상) + +**기존코드** +```swift + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) + collectionView.reloadData() + } +``` + +✅ **해결방법**
+- `collectionView.reloadData()`메서드는 셀을 다시 그리는 메서드인데, `ViewWillApear`에서 실행하게 되면 뷰가 나타나기 전에 셀을 그려서 적용되지 않는 문제였습니다. 아래 코드와 같이 `ViewDidAppear`에서 뷰가 생성된 후 셀을 그리도록 수정하였습니다. + +**현재코드** +```swift + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) + collectionView.reloadData() + } +``` + +### 3️⃣ CoreData에 배열로 저장된 객체 식별하기 +⚠️ **문제점**
+- CoreData에 `Diary`객체가 `Create`될 때 `[Diary]`와 같이 배열로 만들어지고 있었습니다. `Retrieve`할 때 역시 배열로 반환하고 있는데, 이렇게 된다면 특정 객체의 값을 수정하려고 할때 어느 배열에 어떤 값이 있는지 알 수 없었기 때문에 수정과 삭제가 불가능한 문제가 있었습니다. + +**기존코드** +```swift +import CoreData + +extension Diary { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Diary") + } + + @NSManaged public var createdAt: String? + @NSManaged public var title: String? + @NSManaged public var body: String? +} + +extension Diary: Identifiable { +} +``` + +✅ **해결방법**
+- 모델 데이터에 `identifier`라는 변수를 만들고, 데이터가 만들어 질 때 `identifier`에 `UUID`값을 할당하는 방식으로 변경해서 원하는 배열에 접근할 수 있도록 변경하였습니다. + +**현재코드** +```siwft +import CoreData + +extension Diary { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Diary") + } + + @NSManaged public var createdAt: String? + @NSManaged public var title: String? + @NSManaged public var body: String? + @NSManaged public var identifier: String? +} + +extension Diary: Identifiable { +} + +final class CoreDataManager { + ... + func createDiary(_ textView: UITextView) { + ... + object.setValue(UUID().uuidString, forKey: "identifier") + saveContext() + } + ... +} + +``` +
+## 📚 참고자료 - [🍎 Apple Docs: `DateFormatter`](https://developer.apple.com/documentation/foundation/dateformatter) - [🍎 Apple Docs: `NotificationCenter`](https://developer.apple.com/documentation/foundation/notificationcenter) - [🍎 Apple Docs: `keyboardWillShowNotification`](https://developer.apple.com/documentation/uikit/uiresponder/1621576-keyboardwillshownotification) - [🍎 Apple Docs: `keyboardWillHideNotification`](https://developer.apple.com/documentation/uikit/uiresponder/1621606-keyboardwillhidenotification) - [🍎 Apple Docs: `UITextView`](https://developer.apple.com/documentation/uikit/uitextview) +- [🍎 Apple Docs: `CoreData`](https://developer.apple.com/documentation/coredata) +- [🍎 Apple Docs: `UIViewController LifeCycle`](https://developer.apple.com/documentation/uikit/uiviewcontroller#1652793) - [🌐 Blog: `subscript로 안전하게 배열 조회하기`](https://kkimin.tistory.com/86) - [🌐 Blog: `키보드가 텍스트를 가리지 않도록 하기`](https://velog.io/@qudgh849/keyboard가-TextView를-가릴-때) - [🌐 Blog: `identifier 재사용 프로토콜`](https://prod.velog.io/@yyyng/셀-재사용-프로토콜) +- [🌐 Blog: `collectionViewCell Swipe`](https://icksw.tistory.com/291) -
+
## 👬 팀 회고 프로젝트가 끝난 후 작성 예정입니다 (23.09.15) From 378aa1971a632ed4cb1184671cbf24f45a2499b4 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Fri, 8 Sep 2023 14:33:49 +0900 Subject: [PATCH 27/38] =?UTF-8?q?feat:=20=EB=8D=94=20=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiaryDetailViewController.swift | 23 +++++++++++++++++++ Diary/Controller/NewDiaryViewController.swift | 17 ++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 826818537..be8054a45 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -43,6 +43,29 @@ final class DiaryDetailViewController: UIViewController { private func configureNavigation() { navigationItem.title = CoreDataManager.shared.fetchDiary(fetchRequest).first?.createdAt + let seeMoreButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .done, target: self, action: #selector(seeMoreButtonTapped)) + navigationItem.rightBarButtonItem = seeMoreButton + + } + + @objc func seeMoreButtonTapped() { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let shareAction = UIAlertAction(title: "Share", style: .default, handler: { action in + self.showActivityView() + }) + let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: nil) + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + + alertController.addAction(shareAction) + alertController.addAction(deleteAction) + alertController.addAction(cancelAction) + + present(alertController, animated: true) + } + + private func showActivityView() { + let activityViewController = UIActivityViewController(activityItems: ["테스트1", "테스트2"], applicationActivities: nil) + present(activityViewController, animated: true) } private func configureUI() { diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index 15535a272..6b780e87f 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -32,7 +32,24 @@ final class NewDiaryViewController: UIViewController { private func configureNavigation() { navigationItem.title = DateFormatter.today + let seeMoreButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .done, target: self, action: #selector(seeMoreButtonTapped)) + navigationItem.rightBarButtonItem = seeMoreButton today = DateFormatter.today + + } + + @objc func seeMoreButtonTapped() { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + let shareAction = UIAlertAction(title: "Share", style: .default, handler: nil) + let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: nil) + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + + alertController.addAction(shareAction) + alertController.addAction(deleteAction) + alertController.addAction(cancelAction) + + present(alertController, animated: true) + } private func configureUI() { From c5971ad43692282fd0fdde3ea5ab6f8cc02ba5ee Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Fri, 8 Sep 2023 16:29:19 +0900 Subject: [PATCH 28/38] =?UTF-8?q?feat:=20delete=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EB=88=8C=EB=A0=80=EC=9D=84=20=EB=95=8C=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary/Application/SceneDelegate.swift | 2 +- .../DiaryDetailViewController.swift | 32 +++++++++++++++---- Diary/Manager/CoreDataManager.swift | 4 ++- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Diary/Application/SceneDelegate.swift b/Diary/Application/SceneDelegate.swift index 6f73428b3..fa803c807 100644 --- a/Diary/Application/SceneDelegate.swift +++ b/Diary/Application/SceneDelegate.swift @@ -21,6 +21,6 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { } func sceneDidEnterBackground(_ scene: UIScene) { - + print("백그라운드 진입") } } diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index be8054a45..cdb02b7fc 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -9,9 +9,9 @@ import UIKit import CoreData final class DiaryDetailViewController: UIViewController { - private var uuid: String + private let uuid: String private var keyboardManager: KeyboardManager? - var fetchRequest: NSFetchRequest + private let fetchRequest: NSFetchRequest private let textView: UITextView = { let view: UITextView = UITextView() @@ -50,11 +50,15 @@ final class DiaryDetailViewController: UIViewController { @objc func seeMoreButtonTapped() { let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - let shareAction = UIAlertAction(title: "Share", style: .default, handler: { action in + + let shareAction = UIAlertAction(title: "Share", style: .default) { _ in self.showActivityView() - }) - let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: nil) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + } + let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in + self.deleteButtonTapped() + + } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) alertController.addAction(shareAction) alertController.addAction(deleteAction) @@ -63,8 +67,22 @@ final class DiaryDetailViewController: UIViewController { present(alertController, animated: true) } + private func deleteButtonTapped() { + let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) + let cancelAction = UIAlertAction(title: "Cancel", style: .default) + let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in + CoreDataManager.shared.deleteDiary(self.uuid) + self.navigationController?.popViewController(animated: true) + } + + deleteAlertController.addAction(cancelAction) + deleteAlertController.addAction(deleteAction) + + present(deleteAlertController, animated: true) + } + private func showActivityView() { - let activityViewController = UIActivityViewController(activityItems: ["테스트1", "테스트2"], applicationActivities: nil) + let activityViewController = UIActivityViewController(activityItems: ["타이틀 넣어야함"], applicationActivities: nil) present(activityViewController, animated: true) } diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index 61dafab9b..d6f2f9b30 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -30,7 +30,9 @@ final class CoreDataManager { guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: CoreDataManager.shared.context) else { return } + let object = NSManagedObject(entity: entity, insertInto: CoreDataManager.shared.context) + object.setValue("타이틀틀", forKey: "title") object.setValue(textView.text, forKey: "body") object.setValue(DateFormatter.today, forKey: "createdAt") @@ -51,7 +53,6 @@ final class CoreDataManager { // update // delete func deleteDiary(_ uuid: String) { -// let object = diary.first! let fetchRequest = CoreDataManager.shared.receiveFetchRequest(for: uuid) let diary = CoreDataManager.shared.fetchDiary(fetchRequest) @@ -74,6 +75,7 @@ final class CoreDataManager { if context.hasChanges { do { try context.save() + print("세이브 성공") } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") From 153b98e51ef0db885b6b7f97248b3530b8ae4922 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Fri, 8 Sep 2023 16:48:04 +0900 Subject: [PATCH 29/38] =?UTF-8?q?refactor:=20createDiary=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiaryDetailViewController.swift | 4 ++-- .../Controller/DiaryListViewController.swift | 19 ++++++++++++++++++- Diary/Controller/NewDiaryViewController.swift | 15 ++++++++++++++- Diary/Manager/CoreDataManager.swift | 1 - 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index cdb02b7fc..b2186b38d 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -35,6 +35,7 @@ final class DiaryDetailViewController: UIViewController { self.uuid = uuid self.fetchRequest = CoreDataManager.shared.receiveFetchRequest(for: uuid) super.init(nibName: nil, bundle: nil) + } required init?(coder: NSCoder) { @@ -113,7 +114,6 @@ final class DiaryDetailViewController: UIViewController { extension DiaryDetailViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { - CoreDataManager.shared.fetchDiary(fetchRequest).first?.body = textView.text - CoreDataManager.shared.saveContext() + } } diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 41d7a271e..e75172ed9 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -52,6 +52,12 @@ final class DiaryListViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) + diaries.forEach { diary in + if diary.body == nil { + CoreDataManager.shared.deleteDiary(diary.identifier!) + } + } + self.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) collectionView.reloadData() } @@ -83,7 +89,18 @@ final class DiaryListViewController: UIViewController { } @objc private func createNewDiaryButtonTapped() { - let newDiaryViewController: NewDiaryViewController = NewDiaryViewController() + let uuid: String = UUID().uuidString + let newDiaryViewController: NewDiaryViewController = NewDiaryViewController(uuid: uuid) + + guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: CoreDataManager.shared.context) else { + return + } + let object = NSManagedObject(entity: entity, insertInto: CoreDataManager.shared.context) + object.setValue("타이틀틀", forKey: "title") + object.setValue(DateFormatter.today, forKey: "createdAt") + object.setValue(uuid, forKey: "identifier") + + navigationController?.pushViewController(newDiaryViewController, animated: true) } } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index 6b780e87f..68b9b92d4 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -11,6 +11,8 @@ import CoreData final class NewDiaryViewController: UIViewController { private var keyboardManager: KeyboardManager? private var today: String = "" + private var uuid: String + private let fetchRequest: NSFetchRequest private let textView: UITextView = { let view: UITextView = UITextView() @@ -21,6 +23,16 @@ final class NewDiaryViewController: UIViewController { return view }() + init(uuid: String) { + self.uuid = uuid + self.fetchRequest = CoreDataManager.shared.receiveFetchRequest(for: uuid) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() configureNavigation() @@ -78,6 +90,7 @@ final class NewDiaryViewController: UIViewController { extension NewDiaryViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { - CoreDataManager.shared.createDiary(textView) + CoreDataManager.shared.fetchDiary(fetchRequest).first?.body = textView.text + CoreDataManager.shared.saveContext() } } diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index d6f2f9b30..67bcf8d24 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -10,7 +10,6 @@ import CoreData final class CoreDataManager { static let shared: CoreDataManager = CoreDataManager() - private init() {} var context: NSManagedObjectContext { From 0a0fe9227d57ba998ea2bb759709fea3576448d8 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Mon, 11 Sep 2023 20:18:51 +0900 Subject: [PATCH 30/38] =?UTF-8?q?refactor:=20CoreDataManager=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary+CoreDataProperties.swift | 2 +- .../Diary.xcdatamodel/contents | 2 +- Diary/Application/SceneDelegate.swift | 4 - .../DiaryDetailViewController.swift | 12 ++- .../Controller/DiaryListViewController.swift | 12 +-- Diary/Controller/NewDiaryViewController.swift | 3 +- Diary/Extension/Array+.swift | 2 +- Diary/Manager/CoreDataManager.swift | 82 +++++++++++-------- 8 files changed, 65 insertions(+), 54 deletions(-) diff --git a/Diary+CoreDataProperties.swift b/Diary+CoreDataProperties.swift index b1bd562dc..efdf5c996 100644 --- a/Diary+CoreDataProperties.swift +++ b/Diary+CoreDataProperties.swift @@ -18,7 +18,7 @@ extension Diary { @NSManaged public var createdAt: String? @NSManaged public var title: String? @NSManaged public var body: String? - @NSManaged public var identifier: String? + @NSManaged public var identifier: UUID? } extension Diary: Identifiable { diff --git a/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents b/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents index f7829d639..c8826ecde 100644 --- a/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents +++ b/Diary/Application Support/Diary.xcdatamodeld/Diary.xcdatamodel/contents @@ -3,7 +3,7 @@ - + \ No newline at end of file diff --git a/Diary/Application/SceneDelegate.swift b/Diary/Application/SceneDelegate.swift index fa803c807..5a0330793 100644 --- a/Diary/Application/SceneDelegate.swift +++ b/Diary/Application/SceneDelegate.swift @@ -19,8 +19,4 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.rootViewController = navigationController window?.makeKeyAndVisible() } - - func sceneDidEnterBackground(_ scene: UIScene) { - print("백그라운드 진입") - } } diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index b2186b38d..d10bc24bb 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -43,7 +43,7 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - navigationItem.title = CoreDataManager.shared.fetchDiary(fetchRequest).first?.createdAt + navigationItem.title = CoreDataManager.shared.fetch(fetchRequest).first?.createdAt let seeMoreButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .done, target: self, action: #selector(seeMoreButtonTapped)) navigationItem.rightBarButtonItem = seeMoreButton @@ -72,7 +72,7 @@ final class DiaryDetailViewController: UIViewController { let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) let cancelAction = UIAlertAction(title: "Cancel", style: .default) let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in - CoreDataManager.shared.deleteDiary(self.uuid) + CoreDataManager.shared.delete(self.uuid) self.navigationController?.popViewController(animated: true) } @@ -94,7 +94,7 @@ final class DiaryDetailViewController: UIViewController { private func configureTextView() { textView.delegate = self - textView.text = CoreDataManager.shared.fetchDiary(fetchRequest).first?.body + textView.text = CoreDataManager.shared.fetch(fetchRequest).first?.body } private func configureLayout() { @@ -110,10 +110,14 @@ final class DiaryDetailViewController: UIViewController { private func setUpKeyboard() { keyboardManager = KeyboardManager(textView: textView) } + + } extension DiaryDetailViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { - + CoreDataManager.shared.fetch(fetchRequest).first?.body = textView.text + + CoreDataManager.shared.saveContext() } } diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index e75172ed9..0e7c456a5 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -14,10 +14,10 @@ final class DiaryListViewController: UIViewController { var configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) configuration.trailingSwipeActionsConfigurationProvider = { indexPath in let delete = UIContextualAction(style: .destructive, title: "Delete") {[weak self] _, _, completionHandler in - guard let uuid = CoreDataManager.shared.fetchDiary(Diary.fetchRequest())[index: indexPath.item]?.identifier else { return } + guard let uuid = CoreDataManager.shared.fetch(Diary.fetchRequest())[index: indexPath.item]?.identifier else { return } CoreDataManager.shared.deleteDiary(uuid) - self?.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) + self?.diaries = CoreDataManager.shared.fetch(Diary.fetchRequest()) self?.collectionView.reloadData() completionHandler(true) @@ -51,13 +51,13 @@ final class DiaryListViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) + self.diaries = CoreDataManager.shared.fetch(Diary.fetchRequest()) diaries.forEach { diary in if diary.body == nil { CoreDataManager.shared.deleteDiary(diary.identifier!) } } - self.diaries = CoreDataManager.shared.fetchDiary(Diary.fetchRequest()) + self.diaries = CoreDataManager.shared.fetch(Diary.fetchRequest()) collectionView.reloadData() } @@ -115,7 +115,7 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD return UICollectionViewCell() } - guard let diary = diaries[index: indexPath.item] else { + guard let diary = diaries[safe: indexPath.item] else { return cell } @@ -126,7 +126,7 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let uuid = diaries[index: indexPath.item]?.identifier else { + guard let uuid = diaries[safe: indexPath.item]?.identifier else { return } print(uuid) diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift index 68b9b92d4..d0d88da1d 100644 --- a/Diary/Controller/NewDiaryViewController.swift +++ b/Diary/Controller/NewDiaryViewController.swift @@ -90,7 +90,8 @@ final class NewDiaryViewController: UIViewController { extension NewDiaryViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { - CoreDataManager.shared.fetchDiary(fetchRequest).first?.body = textView.text + CoreDataManager.shared.fetch(fetchRequest).first?.body = textView.text + CoreDataManager.shared.saveContext() } } diff --git a/Diary/Extension/Array+.swift b/Diary/Extension/Array+.swift index 753b70626..49cc3c480 100644 --- a/Diary/Extension/Array+.swift +++ b/Diary/Extension/Array+.swift @@ -6,7 +6,7 @@ // extension Array { - subscript(index index: Int) -> Element? { + subscript(safe index: Int) -> Element? { return self.indices ~= index ? self[index] : nil } } diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index 67bcf8d24..585eb0f9e 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -10,36 +10,49 @@ import CoreData final class CoreDataManager { static let shared: CoreDataManager = CoreDataManager() - private init() {} + let fetchRequest: NSFetchRequest = Diary.fetchRequest() + + lazy var persistentContainer: NSPersistentContainer = { + let container = NSPersistentContainer(name: "Diary") + container.loadPersistentStores { _, error in + if let error = error as NSError? { + fatalError("Unresolved error \(error), \(error.userInfo)") + } + } + return container + }() var context: NSManagedObjectContext { return persistentContainer.viewContext } - func receiveFetchRequest(for uuid: String) -> NSFetchRequest { - let fetchRequest: NSFetchRequest = Diary.fetchRequest() - - fetchRequest.predicate = NSPredicate(format: "identifier == %@", uuid) - - return fetchRequest + private init() {} + + func create(diary: Diary) { + let object = Diary(context: context) + object.setValue(diary.title, forKey: "title") + object.setValue(diary.body, forKey: "body") + object.setValue(diary.createdAt, forKey: "createdAt") + object.setValue(diary.identifier, forKey: "identifier") + saveContext() } - // create - func createDiary(_ textView: UITextView) { - guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: CoreDataManager.shared.context) else { - return - } + func fetchAllDiaries() -> [Diary] { + var data: [Diary] = [] + data = fetch(fetchRequest) - let object = NSManagedObject(entity: entity, insertInto: CoreDataManager.shared.context) + return data + } + + func fetchSingleDiary(by uuid: UUID) -> [Diary] { + var data: [Diary] = [] + fetchRequest.predicate = NSPredicate(format: "identifier == %@", uuid.uuidString) + data = fetch(fetchRequest) - object.setValue("타이틀틀", forKey: "title") - object.setValue(textView.text, forKey: "body") - object.setValue(DateFormatter.today, forKey: "createdAt") - object.setValue(UUID().uuidString, forKey: "identifier") - saveContext() + return data } - // fetch - func fetchDiary(_ request: NSFetchRequest) -> [Diary] { + + private func fetch(_ request: NSFetchRequest) -> [Diary] { do { let data = try context.fetch(request) return data @@ -49,25 +62,22 @@ final class CoreDataManager { return [] } - // update - // delete - func deleteDiary(_ uuid: String) { - let fetchRequest = CoreDataManager.shared.receiveFetchRequest(for: uuid) - - let diary = CoreDataManager.shared.fetchDiary(fetchRequest) - persistentContainer.viewContext.delete(diary.first!) + func update(newDiary: Diary) { + var diary = fetchSingleDiary(by: newDiary.identifier!) + diary[safe: 0]!.title = newDiary.title + diary[safe: 0]!.body = newDiary.body + diary[safe: 0]!.createdAt = newDiary.createdAt saveContext() - } + } - lazy var persistentContainer: NSPersistentContainer = { - let container = NSPersistentContainer(name: "Diary") - container.loadPersistentStores { _, error in - if let error = error as NSError? { - fatalError("Unresolved error \(error), \(error.userInfo)") - } + func delete(diary uuid: UUID) { + guard let diary = fetchSingleDiary(by: uuid)[safe: 0] else { + return } - return container - }() + + persistentContainer.viewContext.delete(diary) + saveContext() + } func saveContext() { let context = persistentContainer.viewContext From 300176c254a7ffae566e94fd5dd7762b8c068979 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Mon, 11 Sep 2023 21:33:40 +0900 Subject: [PATCH 31/38] =?UTF-8?q?refactor:=20CRUD=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 8 +- .../DiaryDetailViewController.swift | 100 +++++++++--------- .../Controller/DiaryListViewController.swift | 46 ++++---- Diary/Controller/NewDiaryViewController.swift | 97 ----------------- Diary/Manager/AlertManager.swift | 58 ++++++++++ Diary/Manager/CoreDataManager.swift | 10 +- 6 files changed, 141 insertions(+), 178 deletions(-) delete mode 100644 Diary/Controller/NewDiaryViewController.swift create mode 100644 Diary/Manager/AlertManager.swift diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 6cfa70f5b..d028ab2bb 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */; }; 3BBC989B2A9F1C4C0047DE81 /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC989A2A9F1C4C0047DE81 /* DecodingError.swift */; }; 3BBC989D2A9F2BFA0047DE81 /* Array+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */; }; + 3BBDF5AB2AAF366600E5F256 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBDF5AA2AAF366600E5F256 /* AlertManager.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryListViewController.swift */; }; @@ -25,7 +26,6 @@ C739AE31284DF28600741E8F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C739AE30284DF28600741E8F /* Assets.xcassets */; }; C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C739AE32284DF28600741E8F /* LaunchScreen.storyboard */; }; DC3EA1662A9CAE8400986F72 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */; }; - DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */; }; DC5B192E2AA07FAD0064550A /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */; }; DC5B19302AA08AE70064550A /* LocaleIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5B192F2AA08AE70064550A /* LocaleIdentifier.swift */; }; /* End PBXBuildFile section */ @@ -42,6 +42,7 @@ 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; 3BBC989A2A9F1C4C0047DE81 /* DecodingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingError.swift; sourceTree = ""; }; 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = ""; }; + 3BBDF5AA2AAF366600E5F256 /* AlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -51,7 +52,6 @@ C739AE33284DF28600741E8F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; C739AE35284DF28600741E8F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC3EA1652A9CAE8400986F72 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; - DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDiaryViewController.swift; sourceTree = ""; }; DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; DC5B192F2AA08AE70064550A /* LocaleIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocaleIdentifier.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -108,7 +108,6 @@ children = ( C739AE28284DF28600741E8F /* DiaryListViewController.swift */, 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */, - DC5B191E2A9ED2D90064550A /* NewDiaryViewController.swift */, ); path = Controller; sourceTree = ""; @@ -188,6 +187,7 @@ children = ( DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */, 3B95E4DD2AA5D24B008BFD76 /* CoreDataManager.swift */, + 3BBDF5AA2AAF366600E5F256 /* AlertManager.swift */, ); path = Manager; sourceTree = ""; @@ -302,10 +302,10 @@ 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */, 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, + 3BBDF5AB2AAF366600E5F256 /* AlertManager.swift in Sources */, DC5B19302AA08AE70064550A /* LocaleIdentifier.swift in Sources */, 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, DC5B192E2AA07FAD0064550A /* KeyboardManager.swift in Sources */, - DC5B191F2A9ED2D90064550A /* NewDiaryViewController.swift in Sources */, 3B95E4922AA18B8A008BFD76 /* CellIdentifiable.swift in Sources */, 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, 3B95E4DE2AA5D24B008BFD76 /* CoreDataManager.swift in Sources */, diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index d10bc24bb..9761a6655 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -9,9 +9,8 @@ import UIKit import CoreData final class DiaryDetailViewController: UIViewController { - private let uuid: String + private let diary: Diary? private var keyboardManager: KeyboardManager? - private let fetchRequest: NSFetchRequest private let textView: UITextView = { let view: UITextView = UITextView() @@ -31,11 +30,9 @@ final class DiaryDetailViewController: UIViewController { setUpKeyboard() } - init(uuid: String) { - self.uuid = uuid - self.fetchRequest = CoreDataManager.shared.receiveFetchRequest(for: uuid) + init(diary: Diary) { + self.diary = diary super.init(nibName: nil, bundle: nil) - } required init?(coder: NSCoder) { @@ -43,49 +40,50 @@ final class DiaryDetailViewController: UIViewController { } private func configureNavigation() { - navigationItem.title = CoreDataManager.shared.fetch(fetchRequest).first?.createdAt - let seeMoreButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .done, target: self, action: #selector(seeMoreButtonTapped)) + navigationItem.title = diary.createdAt + let alertManager: AlertManager = AlertManager(uuid: diary.identifier!) + let seeMoreButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .done, target: self, action: #selector(alertManager.seeMoreButtonTapped)) navigationItem.rightBarButtonItem = seeMoreButton - - } - - @objc func seeMoreButtonTapped() { - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - - let shareAction = UIAlertAction(title: "Share", style: .default) { _ in - self.showActivityView() - } - let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in - self.deleteButtonTapped() - - } - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) - - alertController.addAction(shareAction) - alertController.addAction(deleteAction) - alertController.addAction(cancelAction) - - present(alertController, animated: true) } - private func deleteButtonTapped() { - let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) - let cancelAction = UIAlertAction(title: "Cancel", style: .default) - let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in - CoreDataManager.shared.delete(self.uuid) - self.navigationController?.popViewController(animated: true) - } - - deleteAlertController.addAction(cancelAction) - deleteAlertController.addAction(deleteAction) - - present(deleteAlertController, animated: true) - } - private func showActivityView() { - let activityViewController = UIActivityViewController(activityItems: ["타이틀 넣어야함"], applicationActivities: nil) - present(activityViewController, animated: true) - } +// @objc func seeMoreButtonTapped() { +// let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) +// +// let shareAction = UIAlertAction(title: "Share", style: .default) { _ in +// self.showActivityView() +// } +// let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in +// self.deleteButtonTapped() +// +// } +// let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) +// +// alertController.addAction(shareAction) +// alertController.addAction(deleteAction) +// alertController.addAction(cancelAction) +// +// present(alertController, animated: true) +// } +// +// private func deleteButtonTapped() { +// let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) +// let cancelAction = UIAlertAction(title: "Cancel", style: .default) +// let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in +// CoreDataManager.shared.delete(diary: self.diary.identifier!) +// self.navigationController?.popViewController(animated: true) +// } +// +// deleteAlertController.addAction(cancelAction) +// deleteAlertController.addAction(deleteAction) +// +// present(deleteAlertController, animated: true) +// } +// +// func showActivityView() { +// let activityViewController = UIActivityViewController(activityItems: ["타이틀 넣어야함"], applicationActivities: nil) +// present(activityViewController, animated: true) +// } private func configureUI() { view.addSubview(textView) @@ -94,7 +92,11 @@ final class DiaryDetailViewController: UIViewController { private func configureTextView() { textView.delegate = self - textView.text = CoreDataManager.shared.fetch(fetchRequest).first?.body + guard diary.title != nil || diary.body != nil else { + return + } + + textView.text = diary.body } private func configureLayout() { @@ -110,14 +112,10 @@ final class DiaryDetailViewController: UIViewController { private func setUpKeyboard() { keyboardManager = KeyboardManager(textView: textView) } - - } extension DiaryDetailViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { - CoreDataManager.shared.fetch(fetchRequest).first?.body = textView.text - - CoreDataManager.shared.saveContext() + CoreDataManager.shared.update(newDiary: diary) } } diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index 0e7c456a5..fe90732fa 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -13,18 +13,21 @@ final class DiaryListViewController: UIViewController { private lazy var collectionView: UICollectionView = { var configuration: UICollectionLayoutListConfiguration = UICollectionLayoutListConfiguration(appearance: .plain) configuration.trailingSwipeActionsConfigurationProvider = { indexPath in - let delete = UIContextualAction(style: .destructive, title: "Delete") {[weak self] _, _, completionHandler in - guard let uuid = CoreDataManager.shared.fetch(Diary.fetchRequest())[index: indexPath.item]?.identifier else { return } - - CoreDataManager.shared.deleteDiary(uuid) - self?.diaries = CoreDataManager.shared.fetch(Diary.fetchRequest()) + guard let uuid = CoreDataManager.shared.fetchAllDiaries()[indexPath.item].identifier else { + return UISwipeActionsConfiguration() + } + + let delete = UIContextualAction(style: .destructive, title: "Delete") { [weak self] _, _, completionHandler in + CoreDataManager.shared.delete(diary: uuid) + self?.diaries = CoreDataManager.shared.fetchAllDiaries() self?.collectionView.reloadData() completionHandler(true) } let share = UIContextualAction(style: .normal, title: "Share") {_, _, completionHandler in - + let alertManager: AlertManager = AlertManager(uuid: uuid) + alertManager.showActivityView() completionHandler(true) } @@ -51,13 +54,13 @@ final class DiaryListViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.diaries = CoreDataManager.shared.fetch(Diary.fetchRequest()) + self.diaries = CoreDataManager.shared.fetchAllDiaries() diaries.forEach { diary in if diary.body == nil { - CoreDataManager.shared.deleteDiary(diary.identifier!) + CoreDataManager.shared.delete(diary: diary.identifier!) } } - self.diaries = CoreDataManager.shared.fetch(Diary.fetchRequest()) + self.diaries = CoreDataManager.shared.fetchAllDiaries() collectionView.reloadData() } @@ -89,16 +92,18 @@ final class DiaryListViewController: UIViewController { } @objc private func createNewDiaryButtonTapped() { - let uuid: String = UUID().uuidString - let newDiaryViewController: NewDiaryViewController = NewDiaryViewController(uuid: uuid) + let uuid: UUID = UUID() + CoreDataManager.shared.create(diary: uuid) + let diary: Diary = CoreDataManager.shared.fetchSingleDiary(by: uuid)[safe: 0]! + let newDiaryViewController: DiaryDetailViewController = DiaryDetailViewController(diary: diary) - guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: CoreDataManager.shared.context) else { - return - } - let object = NSManagedObject(entity: entity, insertInto: CoreDataManager.shared.context) - object.setValue("타이틀틀", forKey: "title") - object.setValue(DateFormatter.today, forKey: "createdAt") - object.setValue(uuid, forKey: "identifier") +// guard let entity = NSEntityDescription.entity(forEntityName: "Diary", in: CoreDataManager.shared.context) else { +// return +// } +// let object = NSManagedObject(entity: entity, insertInto: CoreDataManager.shared.context) +// object.setValue("타이틀틀", forKey: "title") +// object.setValue(DateFormatter.today, forKey: "createdAt") +// object.setValue(uuid, forKey: "identifier") navigationController?.pushViewController(newDiaryViewController, animated: true) @@ -126,13 +131,12 @@ extension DiaryListViewController: UICollectionViewDataSource, UICollectionViewD func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let uuid = diaries[safe: indexPath.item]?.identifier else { + guard let diary = diaries[safe: indexPath.item] else { return } - print(uuid) collectionView.deselectItem(at: indexPath, animated: true) - let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(uuid: uuid) + let diaryDetailViewController: DiaryDetailViewController = DiaryDetailViewController(diary: diary) navigationController?.pushViewController(diaryDetailViewController, animated: true) } diff --git a/Diary/Controller/NewDiaryViewController.swift b/Diary/Controller/NewDiaryViewController.swift deleted file mode 100644 index d0d88da1d..000000000 --- a/Diary/Controller/NewDiaryViewController.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// NewDiaryViewController.swift -// Diary -// -// Created by idinaloq, yetti on 2023/08/30. -// - -import UIKit -import CoreData - -final class NewDiaryViewController: UIViewController { - private var keyboardManager: KeyboardManager? - private var today: String = "" - private var uuid: String - private let fetchRequest: NSFetchRequest - - private let textView: UITextView = { - let view: UITextView = UITextView() - view.translatesAutoresizingMaskIntoConstraints = false - view.keyboardDismissMode = .interactive - view.alwaysBounceVertical = true - - return view - }() - - init(uuid: String) { - self.uuid = uuid - self.fetchRequest = CoreDataManager.shared.receiveFetchRequest(for: uuid) - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - configureNavigation() - configureUI() - configureLayout() - configureTextView() - setUpKeyboard() - } - - private func configureNavigation() { - navigationItem.title = DateFormatter.today - let seeMoreButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .done, target: self, action: #selector(seeMoreButtonTapped)) - navigationItem.rightBarButtonItem = seeMoreButton - today = DateFormatter.today - - } - - @objc func seeMoreButtonTapped() { - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - let shareAction = UIAlertAction(title: "Share", style: .default, handler: nil) - let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: nil) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - - alertController.addAction(shareAction) - alertController.addAction(deleteAction) - alertController.addAction(cancelAction) - - present(alertController, animated: true) - - } - - private func configureUI() { - view.addSubview(textView) - view.backgroundColor = .systemBackground - } - - private func configureLayout() { - let safeArea = view.safeAreaLayoutGuide - NSLayoutConstraint.activate([ - textView.topAnchor.constraint(equalTo: safeArea.topAnchor), - textView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor), - textView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor), - textView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor) - ]) - } - - private func configureTextView() { - textView.delegate = self - } - - private func setUpKeyboard() { - keyboardManager = KeyboardManager(textView: textView) - } -} - -extension NewDiaryViewController: UITextViewDelegate { - func textViewDidEndEditing(_ textView: UITextView) { - CoreDataManager.shared.fetch(fetchRequest).first?.body = textView.text - - CoreDataManager.shared.saveContext() - } -} diff --git a/Diary/Manager/AlertManager.swift b/Diary/Manager/AlertManager.swift new file mode 100644 index 000000000..d5680216a --- /dev/null +++ b/Diary/Manager/AlertManager.swift @@ -0,0 +1,58 @@ +// +// AlertManager.swift +// Diary +// +// Created by 예찬 on 2023/09/11. +// + +import UIKit + +final class AlertManager: UIViewController { + let uuid: UUID + + init(uuid: UUID) { + self.uuid = uuid + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func seeMoreButtonTapped() { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + let shareAction = UIAlertAction(title: "Share", style: .default) { _ in + self.showActivityView() + } + let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in + self.deleteButtonTapped() + } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) + + alertController.addAction(shareAction) + alertController.addAction(deleteAction) + alertController.addAction(cancelAction) + + present(alertController, animated: true) + } + + func deleteButtonTapped() { + let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) + let cancelAction = UIAlertAction(title: "Cancel", style: .default) + let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in + CoreDataManager.shared.delete(diary: self.uuid) + self.navigationController?.popViewController(animated: true) + } + + deleteAlertController.addAction(cancelAction) + deleteAlertController.addAction(deleteAction) + + present(deleteAlertController, animated: true) + } + + func showActivityView() { + let activityViewController = UIActivityViewController(activityItems: ["타이틀 넣어야함"], applicationActivities: nil) + present(activityViewController, animated: true) + } +} diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index 585eb0f9e..7b84b80d0 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -28,12 +28,12 @@ final class CoreDataManager { private init() {} - func create(diary: Diary) { + func create(diary uuid: UUID) { let object = Diary(context: context) - object.setValue(diary.title, forKey: "title") - object.setValue(diary.body, forKey: "body") - object.setValue(diary.createdAt, forKey: "createdAt") - object.setValue(diary.identifier, forKey: "identifier") +// object.setValue(diary.title, forKey: "title") +// object.setValue(diary.body, forKey: "body") + object.setValue(DateFormatter.today, forKey: "createdAt") + object.setValue(uuid, forKey: "identifier") saveContext() } From abb11c3646c214d98ee2928fe878ec55397c5806 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Tue, 12 Sep 2023 11:54:06 +0900 Subject: [PATCH 32/38] =?UTF-8?q?refactor:=20AlertManager=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95,=20DetailViewC?= =?UTF-8?q?ontroller=EC=9D=98=20body,=20title=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiaryDetailViewController.swift | 128 ++++++++++++------ .../Controller/DiaryListViewController.swift | 22 ++- Diary/Manager/AlertManager.swift | 36 +---- Diary/Manager/CoreDataManager.swift | 22 +-- 4 files changed, 108 insertions(+), 100 deletions(-) diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 9761a6655..ac807431c 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -9,11 +9,12 @@ import UIKit import CoreData final class DiaryDetailViewController: UIViewController { - private let diary: Diary? + private let diary: Diary private var keyboardManager: KeyboardManager? private let textView: UITextView = { let view: UITextView = UITextView() + view.font = UIFont.systemFont(ofSize: 17.0) view.translatesAutoresizingMaskIntoConstraints = false view.keyboardDismissMode = .interactive view.alwaysBounceVertical = true @@ -41,50 +42,45 @@ final class DiaryDetailViewController: UIViewController { private func configureNavigation() { navigationItem.title = diary.createdAt - let alertManager: AlertManager = AlertManager(uuid: diary.identifier!) - let seeMoreButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .done, target: self, action: #selector(alertManager.seeMoreButtonTapped)) + let seeMoreButton: UIBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), style: .done, target: self, action: #selector(seeMoreButtonTapped)) navigationItem.rightBarButtonItem = seeMoreButton } + + @objc func seeMoreButtonTapped() { + let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + let shareAction = UIAlertAction(title: "Share", style: .default) { _ in + self.showActivityView() + } + let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in + self.deleteButtonTapped() + } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) + + alertController.addAction(shareAction) + alertController.addAction(deleteAction) + alertController.addAction(cancelAction) + present(alertController, animated: true) + } + func deleteButtonTapped() { + let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) + let cancelAction = UIAlertAction(title: "Cancel", style: .default) + let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in + CoreDataManager.shared.delete(diary: self.diary.identifier!) + self.navigationController?.popViewController(animated: true) + } + + deleteAlertController.addAction(cancelAction) + deleteAlertController.addAction(deleteAction) + present(deleteAlertController, animated: true) + } -// @objc func seeMoreButtonTapped() { -// let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) -// -// let shareAction = UIAlertAction(title: "Share", style: .default) { _ in -// self.showActivityView() -// } -// let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in -// self.deleteButtonTapped() -// -// } -// let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) -// -// alertController.addAction(shareAction) -// alertController.addAction(deleteAction) -// alertController.addAction(cancelAction) -// -// present(alertController, animated: true) -// } -// -// private func deleteButtonTapped() { -// let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) -// let cancelAction = UIAlertAction(title: "Cancel", style: .default) -// let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in -// CoreDataManager.shared.delete(diary: self.diary.identifier!) -// self.navigationController?.popViewController(animated: true) -// } -// -// deleteAlertController.addAction(cancelAction) -// deleteAlertController.addAction(deleteAction) -// -// present(deleteAlertController, animated: true) -// } -// -// func showActivityView() { -// let activityViewController = UIActivityViewController(activityItems: ["타이틀 넣어야함"], applicationActivities: nil) -// present(activityViewController, animated: true) -// } - + func showActivityView() { + let activityViewController = UIActivityViewController(activityItems: ["타이틀 넣어야함"], applicationActivities: nil) + present(activityViewController, animated: true) + } + private func configureUI() { view.addSubview(textView) view.backgroundColor = .systemBackground @@ -92,11 +88,12 @@ final class DiaryDetailViewController: UIViewController { private func configureTextView() { textView.delegate = self - guard diary.title != nil || diary.body != nil else { + guard let title = diary.title, + let body = diary.body else { return } - textView.text = diary.body + textView.text = title + body } private func configureLayout() { @@ -115,7 +112,52 @@ final class DiaryDetailViewController: UIViewController { } extension DiaryDetailViewController: UITextViewDelegate { + func updateTitle(by textView: UITextView) -> String { + let textViewWidth = Int(textView.frame.width) + let textViewFontSize = 17 + let titleLength: Int = textViewWidth / textViewFontSize +// print(textViewWidth) +// print(textViewFontSize) +// print(titleLength) + let title = String(textView.text.prefix(Int(titleLength))) + + return title + } + + func updateBody(by textView: UITextView) -> String { + let textViewWidth = Int(textView.frame.width) + let textViewFontSize = 17 + let startBodyLength: Int = textViewWidth / textViewFontSize + let endIndex = textView.text.index(textView.text.endIndex, offsetBy: -startBodyLength) + let body = String(textView.text.suffix(from: endIndex)) + + return body + } + + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + <#code#> + } + func textViewDidEndEditing(_ textView: UITextView) { +// diary.title = updateTitle(by: textView) +// +// diary.body = updateBody(by: textView) + guard let text = textView.text else { return } + let lines = text.components(separatedBy: "\n") + if lines.count > 0 { + let title = lines[0] + diary.title = title + var content = "" + if lines.count > 1 { + for iii in 1.. = Diary.fetchRequest() +// let fetchRequest: NSFetchRequest = Diary.fetchRequest() lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "Diary") @@ -30,21 +30,23 @@ final class CoreDataManager { func create(diary uuid: UUID) { let object = Diary(context: context) -// object.setValue(diary.title, forKey: "title") -// object.setValue(diary.body, forKey: "body") + // object.setValue(diary.title, forKey: "title") + // object.setValue(diary.body, forKey: "body") object.setValue(DateFormatter.today, forKey: "createdAt") object.setValue(uuid, forKey: "identifier") saveContext() } func fetchAllDiaries() -> [Diary] { + let fetchRequest: NSFetchRequest = Diary.fetchRequest() var data: [Diary] = [] data = fetch(fetchRequest) - + return data } func fetchSingleDiary(by uuid: UUID) -> [Diary] { + let fetchRequest: NSFetchRequest = Diary.fetchRequest() var data: [Diary] = [] fetchRequest.predicate = NSPredicate(format: "identifier == %@", uuid.uuidString) data = fetch(fetchRequest) @@ -55,6 +57,7 @@ final class CoreDataManager { private func fetch(_ request: NSFetchRequest) -> [Diary] { do { let data = try context.fetch(request) + return data } catch { print(error.localizedDescription) @@ -63,10 +66,13 @@ final class CoreDataManager { } func update(newDiary: Diary) { - var diary = fetchSingleDiary(by: newDiary.identifier!) - diary[safe: 0]!.title = newDiary.title - diary[safe: 0]!.body = newDiary.body - diary[safe: 0]!.createdAt = newDiary.createdAt + guard let uuid = newDiary.identifier, + let diary = fetchSingleDiary(by: uuid)[safe: 0] else { return } + + diary.title = newDiary.title + diary.body = newDiary.body + diary.createdAt = newDiary.createdAt + saveContext() } From 43d423d7628941f60de0c2a2519e14929bd7e19e Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Tue, 12 Sep 2023 17:42:45 +0900 Subject: [PATCH 33/38] =?UTF-8?q?refactor:=20textView=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20CRUD=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary/Error/DecodingError.swift | 19 ------------------- Diary/Manager/AlertManager.swift | 24 ------------------------ Diary/Model/DiaryEntity.swift | 12 ------------ 3 files changed, 55 deletions(-) delete mode 100644 Diary/Error/DecodingError.swift delete mode 100644 Diary/Manager/AlertManager.swift delete mode 100644 Diary/Model/DiaryEntity.swift diff --git a/Diary/Error/DecodingError.swift b/Diary/Error/DecodingError.swift deleted file mode 100644 index 88823fb5d..000000000 --- a/Diary/Error/DecodingError.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// DecodingError.swift -// Diary -// -// Created by idinaloq, yetti on 2023/08/30. -// - -import Foundation - -enum DecodingError: LocalizedError { - case decodingFailure - - var errorDescription: String? { - switch self { - case .decodingFailure: - return "디코딩 에러입니다." - } - } -} diff --git a/Diary/Manager/AlertManager.swift b/Diary/Manager/AlertManager.swift deleted file mode 100644 index c21ce782d..000000000 --- a/Diary/Manager/AlertManager.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// AlertManager.swift -// Diary -// -// Created by 예찬 on 2023/09/11. -// - -import UIKit - -final class AlertManager: UIViewController { - let uuid: UUID - - init(uuid: UUID) { - self.uuid = uuid - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - - -} diff --git a/Diary/Model/DiaryEntity.swift b/Diary/Model/DiaryEntity.swift deleted file mode 100644 index d5c66554b..000000000 --- a/Diary/Model/DiaryEntity.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// DiaryEntity.swift -// Diary -// -// Created by idinaloq, yetti on 2023/08/29. -// - -struct DiaryEntity { - let title, body: String - let createdAt: String - let identifier: Int -} From cedb035c89209bdd39b0acf49aaf36c6779035c7 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Wed, 13 Sep 2023 09:05:54 +0900 Subject: [PATCH 34/38] =?UTF-8?q?refactor:=20TextView=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 20 ------ .../DiaryDetailViewController.swift | 72 +++++++------------ .../Controller/DiaryListViewController.swift | 28 ++++---- Diary/Extension/DateFormatter+.swift | 11 +-- Diary/Manager/CoreDataManager.swift | 16 ----- Diary/Manager/KeyboardManager.swift | 2 +- 6 files changed, 42 insertions(+), 107 deletions(-) diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index d028ab2bb..844cc8c67 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -13,12 +13,9 @@ 3B95E4E52AA6E156008BFD76 /* Diary+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4E32AA6E155008BFD76 /* Diary+CoreDataClass.swift */; }; 3B95E4E62AA6E156008BFD76 /* Diary+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4E42AA6E155008BFD76 /* Diary+CoreDataProperties.swift */; }; 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */; }; - 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */; }; 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */; }; 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */; }; - 3BBC989B2A9F1C4C0047DE81 /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC989A2A9F1C4C0047DE81 /* DecodingError.swift */; }; 3BBC989D2A9F2BFA0047DE81 /* Array+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */; }; - 3BBDF5AB2AAF366600E5F256 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BBDF5AA2AAF366600E5F256 /* AlertManager.swift */; }; C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE24284DF28600741E8F /* AppDelegate.swift */; }; C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE26284DF28600741E8F /* SceneDelegate.swift */; }; C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C739AE28284DF28600741E8F /* DiaryListViewController.swift */; }; @@ -37,12 +34,9 @@ 3B95E4E32AA6E155008BFD76 /* Diary+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diary+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; 3B95E4E42AA6E155008BFD76 /* Diary+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diary+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; 3BBC988F2A9D73D70047DE81 /* DiaryCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewListCell.swift; sourceTree = ""; }; - 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryEntity.swift; sourceTree = ""; }; 3BBC98952A9DC5330047DE81 /* DiaryDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryDetailViewController.swift; sourceTree = ""; }; 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+.swift"; sourceTree = ""; }; - 3BBC989A2A9F1C4C0047DE81 /* DecodingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingError.swift; sourceTree = ""; }; 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = ""; }; - 3BBDF5AA2AAF366600E5F256 /* AlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; C739AE21284DF28600741E8F /* Diary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Diary.app; sourceTree = BUILT_PRODUCTS_DIR; }; C739AE24284DF28600741E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C739AE26284DF28600741E8F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -124,7 +118,6 @@ 3BBC98922A9D897B0047DE81 /* Model */ = { isa = PBXGroup; children = ( - 3BBC98932A9D89D20047DE81 /* DiaryEntity.swift */, ); path = Model; sourceTree = ""; @@ -139,14 +132,6 @@ path = Extension; sourceTree = ""; }; - 3BBC989E2A9F2C6F0047DE81 /* Error */ = { - isa = PBXGroup; - children = ( - 3BBC989A2A9F1C4C0047DE81 /* DecodingError.swift */, - ); - path = Error; - sourceTree = ""; - }; C739AE18284DF28600741E8F = { isa = PBXGroup; children = ( @@ -172,7 +157,6 @@ 3B95E4902AA18B74008BFD76 /* Protocol */, DC5B19312AA08AEB0064550A /* Enum */, DC5B192C2AA07FA10064550A /* Manager */, - 3BBC989E2A9F2C6F0047DE81 /* Error */, 3BBC98972A9E44820047DE81 /* Extension */, 3BBC98922A9D897B0047DE81 /* Model */, 3BBC988E2A9D683C0047DE81 /* View */, @@ -187,7 +171,6 @@ children = ( DC5B192D2AA07FAD0064550A /* KeyboardManager.swift */, 3B95E4DD2AA5D24B008BFD76 /* CoreDataManager.swift */, - 3BBDF5AA2AAF366600E5F256 /* AlertManager.swift */, ); path = Manager; sourceTree = ""; @@ -302,15 +285,12 @@ 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */, 3BBC98992A9E449E0047DE81 /* DateFormatter+.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, - 3BBDF5AB2AAF366600E5F256 /* AlertManager.swift in Sources */, DC5B19302AA08AE70064550A /* LocaleIdentifier.swift in Sources */, 3BBC98902A9D73D70047DE81 /* DiaryCollectionViewListCell.swift in Sources */, DC5B192E2AA07FAD0064550A /* KeyboardManager.swift in Sources */, 3B95E4922AA18B8A008BFD76 /* CellIdentifiable.swift in Sources */, - 3BBC98942A9D89D20047DE81 /* DiaryEntity.swift in Sources */, 3B95E4DE2AA5D24B008BFD76 /* CoreDataManager.swift in Sources */, 3B95E4E62AA6E156008BFD76 /* Diary+CoreDataProperties.swift in Sources */, - 3BBC989B2A9F1C4C0047DE81 /* DecodingError.swift in Sources */, 3B95E4E52AA6E156008BFD76 /* Diary+CoreDataClass.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index ac807431c..9be8169ed 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -6,7 +6,6 @@ // import UIKit -import CoreData final class DiaryDetailViewController: UIViewController { private let diary: Diary @@ -47,10 +46,11 @@ final class DiaryDetailViewController: UIViewController { } @objc func seeMoreButtonTapped() { + let title = diary.title let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let shareAction = UIAlertAction(title: "Share", style: .default) { _ in - self.showActivityView() + self.showActivityView(title) } let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in self.deleteButtonTapped() @@ -67,7 +67,11 @@ final class DiaryDetailViewController: UIViewController { let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) let cancelAction = UIAlertAction(title: "Cancel", style: .default) let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in - CoreDataManager.shared.delete(diary: self.diary.identifier!) + guard let identifier = self.diary.identifier else { + return + } + + CoreDataManager.shared.delete(diary: identifier) self.navigationController?.popViewController(animated: true) } @@ -76,8 +80,8 @@ final class DiaryDetailViewController: UIViewController { present(deleteAlertController, animated: true) } - func showActivityView() { - let activityViewController = UIActivityViewController(activityItems: ["타이틀 넣어야함"], applicationActivities: nil) + func showActivityView(_ diary: String?) { + let activityViewController = UIActivityViewController(activityItems: [diary as Any], applicationActivities: nil) present(activityViewController, animated: true) } @@ -93,7 +97,7 @@ final class DiaryDetailViewController: UIViewController { return } - textView.text = title + body + textView.text = title + "\n" + body } private func configureLayout() { @@ -112,52 +116,24 @@ final class DiaryDetailViewController: UIViewController { } extension DiaryDetailViewController: UITextViewDelegate { - func updateTitle(by textView: UITextView) -> String { - let textViewWidth = Int(textView.frame.width) - let textViewFontSize = 17 - let titleLength: Int = textViewWidth / textViewFontSize -// print(textViewWidth) -// print(textViewFontSize) -// print(titleLength) - let title = String(textView.text.prefix(Int(titleLength))) - - return title - } - - func updateBody(by textView: UITextView) -> String { - let textViewWidth = Int(textView.frame.width) - let textViewFontSize = 17 - let startBodyLength: Int = textViewWidth / textViewFontSize - let endIndex = textView.text.index(textView.text.endIndex, offsetBy: -startBodyLength) - let body = String(textView.text.suffix(from: endIndex)) - - return body - } - - func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - <#code#> - } - func textViewDidEndEditing(_ textView: UITextView) { -// diary.title = updateTitle(by: textView) -// -// diary.body = updateBody(by: textView) - guard let text = textView.text else { return } + var body = "" + + guard let text = textView.text, + !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + return + } + let lines = text.components(separatedBy: "\n") - if lines.count > 0 { - let title = lines[0] - diary.title = title - var content = "" - if lines.count > 1 { - for iii in 1.. 1 { + for line in 1.. String { -// let date: Date = Date(timeIntervalSince1970: TimeInterval(data.createdAt)) -// formatter.locale = Locale(identifier: locale.description) -// formatter.timeZone = TimeZone(identifier: TimeZone.current.identifier) -// formatter.dateStyle = .long -// -// return formatter.string(from: date) -// } } diff --git a/Diary/Manager/CoreDataManager.swift b/Diary/Manager/CoreDataManager.swift index 9996af6bd..ceb6514b0 100644 --- a/Diary/Manager/CoreDataManager.swift +++ b/Diary/Manager/CoreDataManager.swift @@ -10,7 +10,6 @@ import CoreData final class CoreDataManager { static let shared: CoreDataManager = CoreDataManager() -// let fetchRequest: NSFetchRequest = Diary.fetchRequest() lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "Diary") @@ -30,8 +29,6 @@ final class CoreDataManager { func create(diary uuid: UUID) { let object = Diary(context: context) - // object.setValue(diary.title, forKey: "title") - // object.setValue(diary.body, forKey: "body") object.setValue(DateFormatter.today, forKey: "createdAt") object.setValue(uuid, forKey: "identifier") saveContext() @@ -57,25 +54,13 @@ final class CoreDataManager { private func fetch(_ request: NSFetchRequest) -> [Diary] { do { let data = try context.fetch(request) - return data } catch { print(error.localizedDescription) } return [] } - - func update(newDiary: Diary) { - guard let uuid = newDiary.identifier, - let diary = fetchSingleDiary(by: uuid)[safe: 0] else { return } - - diary.title = newDiary.title - diary.body = newDiary.body - diary.createdAt = newDiary.createdAt - saveContext() - } - func delete(diary uuid: UUID) { guard let diary = fetchSingleDiary(by: uuid)[safe: 0] else { return @@ -90,7 +75,6 @@ final class CoreDataManager { if context.hasChanges { do { try context.save() - print("세이브 성공") } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") diff --git a/Diary/Manager/KeyboardManager.swift b/Diary/Manager/KeyboardManager.swift index 44fbae70a..c94097cf5 100644 --- a/Diary/Manager/KeyboardManager.swift +++ b/Diary/Manager/KeyboardManager.swift @@ -2,7 +2,7 @@ // KeyboardManager.swift // Diary // -// Created by idinaloq, yetti on 2023/08/31. +// Created by idinaloq, yetti on 2023/08/31. // import UIKit From dc84a761cacf6129d8ac9531238e5dde1854cac0 Mon Sep 17 00:00:00 2001 From: idinaloq Date: Fri, 15 Sep 2023 17:44:51 +0900 Subject: [PATCH 35/38] =?UTF-8?q?docs:=203=EC=A3=BC=EC=B0=A8=20README=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary.xcodeproj/project.pbxproj | 8 --- README.md | 95 ++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 844cc8c67..754edb21b 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -115,13 +115,6 @@ path = View; sourceTree = ""; }; - 3BBC98922A9D897B0047DE81 /* Model */ = { - isa = PBXGroup; - children = ( - ); - path = Model; - sourceTree = ""; - }; 3BBC98972A9E44820047DE81 /* Extension */ = { isa = PBXGroup; children = ( @@ -158,7 +151,6 @@ DC5B19312AA08AEB0064550A /* Enum */, DC5B192C2AA07FA10064550A /* Manager */, 3BBC98972A9E44820047DE81 /* Extension */, - 3BBC98922A9D897B0047DE81 /* Model */, 3BBC988E2A9D683C0047DE81 /* View */, 3BBC988D2A9D68290047DE81 /* Controller */, 3BBC988C2A9D67EC0047DE81 /* Resource */, diff --git a/README.md b/README.md index bcf4cec45..5db8d7cb6 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,23 @@ # 📓 일기장 -### 일기를 생성하고 작성 후에 저장 및 삭제할 수 있는 앱입니다. - -> **핵심 개념 및 경험** -> -> - **DateFormatter** -> - `locale` 프로퍼티를 이용한 지역화 -> - **CoreData** -> - `CoreData`모델을 통한 CRUD 기능 -> - (Create, Read(Retrieve), Update, Delete) -> - **UITextView** -> - `UITextView`에서 텍스트 편집 -> - **keyboardWillShowNotification / keyboardWillHideNotification** -> - 키보드가 나타나거나 사라질 때 `post`된 `Notification`을 `addObserver`를 통해 수신 -> - **subscript** -> - 배열의 범위를 벗어난 접근을 할 때 안전하게 접근할 수 있도록 `subscript`를 사용하여 `Array`의 기능 확장 +## 일기를 생성하고 작성 후에 저장 및 삭제할 수 있는 앱입니다. + +**핵심 개념 및 경험** + +- **DateFormatter** + - `locale` 프로퍼티를 이용한 지역화 +- **CoreData** + - `CoreData`모델을 통한 CRUD 기능 + - (Create, Read(Retrieve), Update, Delete) +- **UITextView** + - `UITextView`에서 텍스트 편집 +- **keyboardWillShowNotification / keyboardWillHideNotification** + - 키보드가 나타나거나 사라질 때 `post`된 `Notification`을 `addObserver`를 통해 수신 +- **subscript** + - 배열의 범위를 벗어난 접근을 할 때 안전하게 접근할 수 있도록 `subscript`를 사용하여 `Array`의 기능 확장 +- **textViewDidEndEditing()** + - `UITextView`의 입력이 끝났을 때 `UITextViewDelegate`를 통해 실행되는 메서드 +- **UIAlertAction** + - 얼럿 버튼을 눌렀을 때 실행되는 액션 **프로젝트 기간 : 23.08.28 ~ 23.09.15** @@ -48,6 +52,10 @@ |2023.09.06|CoreData의 Create,Retieve,Update기능 구현
CoreData관련코드 리팩토링| |2023.09.07|CoreData의 Delete기능 추가
swipe기능 구현| |2023.09.08|README 작성| +|2023.09.11|CoreDataManager 리팩토링| +|2023.09.12|textView 데이터 CRUD 기능 리팩토링| +|2023.09.13|Step2 PR 작성| +|2023.09.15|Stpe2 리뷰에 따른 수정
README 작성|
## 👀 시각화 구조 @@ -60,8 +68,7 @@ │   └── Diary.xcdatamodeld  ├── Controller │   ├── DiaryDetailViewController.swift - │   ├── DiaryListViewController.swift - │   └── NewDiaryViewController.swift + │   └── DiaryListViewController.swift ├── Enum │   └── LocaleIdentifier.swift ├── Error @@ -73,8 +80,6 @@ ├── Manager │   ├── CoreDataManager.swift │   └── KeyboardManager.swift - ├── Model - │   └── DiaryEntity.swift ├── Protocol │   └── CellIdentifiable.swift ├── View @@ -89,14 +94,15 @@ ### 2. 클래스 다이어그램 -![일기장 다이어그램](https://github.com/iOS-Yetti/ios-diary/assets/100982422/eb223cb6-a8ee-40ad-ba2d-ed0dcde88432) +![일기장 UML](https://hackmd.io/_uploads/rk74B9Wyp.png)
## 💻 실행화면 -|실행화면(세로)| -|:---:| -|| +|일기 생성화면|스와이프 액션기능|더보기 버튼| +|:-----:|:-----------:|:--------:| +|||| + |실행화면(가로)| |:---:| @@ -243,6 +249,39 @@ final class CoreDataManager { } ``` + +### 4️⃣ 두 개의 ViewController를 하나로 합치기 +⚠️ **문제점**
+- 이전에 뷰컨트롤러는 목록을 보여주는 `DiaryListViewController`, 작성된 일기 내용을 보여주는 `DiaryDetailViewController`, 일기장을 새로 만드는 `NewDiaryViewController` 총 세 개가 있었습니다. +- 일기를 새로 생성하거나, 수정하는 화면을 볼 때 구성 자체는 완전히 동일했고, 기존의 저장된 데이터를 보여주거나 데이터가 없다면 새로 생성해야되는 부분 이외에 차이는 없었습니다. + + +✅ **해결방법**
+- `NewDiaryViewController`를 `DiaryDetailViewController`에 통합시키고, 기존에 `DiaryDetailViewController`의 수정, 저장 기능만 사용하는 로직을 그대로 사용하였습니다. +- 새로운 일기를 미리 생성해서 작성화면으로 넘겨주고, 저장을 하게 되는데, 만약 아무런 내용도 입력하지 않았다면 다시 `DiaryListViewController`로 넘어올 때 생성되었던 일기가 삭제되도록 다음과 같이 작성하였습니다. +```swift +final class DiaryListViewController: UIViewController { + ... + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.diaries = CoreDataManager.shared.fetchAllDiaries() + + diaries.forEach { diary in + if diary.title == nil && diary.body == nil { + guard let identifier = diary.identifier else { + return + } + + CoreDataManager.shared.delete(diary: identifier) + } + } + self.diaries = CoreDataManager.shared.fetchAllDiaries() + collectionView.reloadData() + } + ... +} +``` +
## 📚 참고자료 - [🍎 Apple Docs: `DateFormatter`](https://developer.apple.com/documentation/foundation/dateformatter) @@ -252,6 +291,7 @@ final class CoreDataManager { - [🍎 Apple Docs: `UITextView`](https://developer.apple.com/documentation/uikit/uitextview) - [🍎 Apple Docs: `CoreData`](https://developer.apple.com/documentation/coredata) - [🍎 Apple Docs: `UIViewController LifeCycle`](https://developer.apple.com/documentation/uikit/uiviewcontroller#1652793) +- [🍎 Apple Docs: `UUID`](https://developer.apple.com/documentation/foundation/uuid) - [🌐 Blog: `subscript로 안전하게 배열 조회하기`](https://kkimin.tistory.com/86) - [🌐 Blog: `키보드가 텍스트를 가리지 않도록 하기`](https://velog.io/@qudgh849/keyboard가-TextView를-가릴-때) - [🌐 Blog: `identifier 재사용 프로토콜`](https://prod.velog.io/@yyyng/셀-재사용-프로토콜) @@ -259,4 +299,11 @@ final class CoreDataManager {
## 👬 팀 회고 -프로젝트가 끝난 후 작성 예정입니다 (23.09.15) +### To. Idinaloq +- 시간을 잘 맞춰주셔서 좋았습니다 +- 서로 솔직한 의견을 잘 나눌 수 있었던 것 같아 좋았습니다 +- 프로젝트를 마무리 짓지 못한 부분은 아쉽습니다 ㅠㅠ + +### To. Yetti +- 짧은 시간동안 집중해서 팀 프로젝트를 진행하니 효율이 좋다는걸 처음으로 느꼈네요.😄 +- 서로 일정이 있을때 편의를 봐주셔서 너무 좋았습니다!!! 👍 From 61b345503e1d428e4fffada04d3cd94fa1f09a3e Mon Sep 17 00:00:00 2001 From: idinaloq <124647187+idinaloq@users.noreply.github.com> Date: Fri, 15 Sep 2023 17:45:43 +0900 Subject: [PATCH 36/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5db8d7cb6..6faea790d 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ ### 2. 클래스 다이어그램 -![일기장 UML](https://hackmd.io/_uploads/rk74B9Wyp.png) +![일기장 UML](https://hackmd.io/_uploads/rk74B9Wyp.png)
## 💻 실행화면 From 1955a28baccc6b921b75be0153ca72cbcb3b5a10 Mon Sep 17 00:00:00 2001 From: idinaloq <124647187+idinaloq@users.noreply.github.com> Date: Fri, 15 Sep 2023 17:51:50 +0900 Subject: [PATCH 37/38] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6faea790d..6a1e38ec5 100644 --- a/README.md +++ b/README.md @@ -94,14 +94,14 @@ ### 2. 클래스 다이어그램 -![일기장 UML](https://hackmd.io/_uploads/rk74B9Wyp.png) +![일기장 UML](https://github.com/idinaloq/testRep/assets/124647187/1daf56e3-d2b5-4ca6-b7d8-8646f29e3cab)
## 💻 실행화면 |일기 생성화면|스와이프 액션기능|더보기 버튼| |:-----:|:-----------:|:--------:| -|||| +|||| |실행화면(가로)| From ee1e8cc4d9a7579319f514b96e87a372734fe6a4 Mon Sep 17 00:00:00 2001 From: iOS-Yetti Date: Sat, 16 Sep 2023 15:25:14 +0900 Subject: [PATCH 38/38] =?UTF-8?q?refactor:=20textViewDidEndEditing()=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Diary+CoreDataProperties.swift | 2 +- Diary.xcodeproj/project.pbxproj | 4 -- .../DiaryDetailViewController.swift | 39 +++++++-------- .../Controller/DiaryListViewController.swift | 50 +++++++++---------- Diary/Extension/CellIdentifiable+.swift | 12 ----- Diary/Protocol/CellIdentifiable.swift | 6 +++ 6 files changed, 51 insertions(+), 62 deletions(-) delete mode 100644 Diary/Extension/CellIdentifiable+.swift diff --git a/Diary+CoreDataProperties.swift b/Diary+CoreDataProperties.swift index efdf5c996..4dbd97007 100644 --- a/Diary+CoreDataProperties.swift +++ b/Diary+CoreDataProperties.swift @@ -18,7 +18,7 @@ extension Diary { @NSManaged public var createdAt: String? @NSManaged public var title: String? @NSManaged public var body: String? - @NSManaged public var identifier: UUID? + @NSManaged public var identifier: UUID } extension Diary: Identifiable { diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 754edb21b..c6e1c40c5 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 3B95E4922AA18B8A008BFD76 /* CellIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4912AA18B8A008BFD76 /* CellIdentifiable.swift */; }; - 3B95E4942AA18C44008BFD76 /* CellIdentifiable+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4932AA18C44008BFD76 /* CellIdentifiable+.swift */; }; 3B95E4DE2AA5D24B008BFD76 /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4DD2AA5D24B008BFD76 /* CoreDataManager.swift */; }; 3B95E4E52AA6E156008BFD76 /* Diary+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4E32AA6E155008BFD76 /* Diary+CoreDataClass.swift */; }; 3B95E4E62AA6E156008BFD76 /* Diary+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B95E4E42AA6E155008BFD76 /* Diary+CoreDataProperties.swift */; }; @@ -29,7 +28,6 @@ /* Begin PBXFileReference section */ 3B95E4912AA18B8A008BFD76 /* CellIdentifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellIdentifiable.swift; sourceTree = ""; }; - 3B95E4932AA18C44008BFD76 /* CellIdentifiable+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CellIdentifiable+.swift"; sourceTree = ""; }; 3B95E4DD2AA5D24B008BFD76 /* CoreDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; 3B95E4E32AA6E155008BFD76 /* Diary+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diary+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; 3B95E4E42AA6E155008BFD76 /* Diary+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Diary+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; @@ -118,7 +116,6 @@ 3BBC98972A9E44820047DE81 /* Extension */ = { isa = PBXGroup; children = ( - 3B95E4932AA18C44008BFD76 /* CellIdentifiable+.swift */, 3BBC98982A9E449E0047DE81 /* DateFormatter+.swift */, 3BBC989C2A9F2BFA0047DE81 /* Array+.swift */, ); @@ -271,7 +268,6 @@ files = ( C739AE29284DF28600741E8F /* DiaryListViewController.swift in Sources */, 3BBC989D2A9F2BFA0047DE81 /* Array+.swift in Sources */, - 3B95E4942AA18C44008BFD76 /* CellIdentifiable+.swift in Sources */, C739AE25284DF28600741E8F /* AppDelegate.swift in Sources */, C739AE27284DF28600741E8F /* SceneDelegate.swift in Sources */, 3BBC98962A9DC5330047DE81 /* DiaryDetailViewController.swift in Sources */, diff --git a/Diary/Controller/DiaryDetailViewController.swift b/Diary/Controller/DiaryDetailViewController.swift index 9be8169ed..d610a8f38 100644 --- a/Diary/Controller/DiaryDetailViewController.swift +++ b/Diary/Controller/DiaryDetailViewController.swift @@ -67,11 +67,7 @@ final class DiaryDetailViewController: UIViewController { let deleteAlertController = UIAlertController(title: "Really??", message: "Think one more", preferredStyle: .alert) let cancelAction = UIAlertAction(title: "Cancel", style: .default) let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in - guard let identifier = self.diary.identifier else { - return - } - - CoreDataManager.shared.delete(diary: identifier) + CoreDataManager.shared.delete(diary: self.diary.identifier) self.navigationController?.popViewController(animated: true) } @@ -116,24 +112,27 @@ final class DiaryDetailViewController: UIViewController { } extension DiaryDetailViewController: UITextViewDelegate { - func textViewDidEndEditing(_ textView: UITextView) { - var body = "" - - guard let text = textView.text, - !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { - return + private func splitText() -> (title: String, body: String)? { + guard let text = textView.text?.trimmingCharacters(in: .whitespacesAndNewlines), + !text.isEmpty else { + return nil } let lines = text.components(separatedBy: "\n") - diary.title = lines[0] - - if lines.count > 1 { - for line in 1..