diff --git a/.gitignore b/.gitignore index d436556e4..789967a58 100644 --- a/.gitignore +++ b/.gitignore @@ -68,6 +68,7 @@ xcuserdata/ *.moved-aside *.xccheckout *.xcscmblueprint +Key.plist ## Obj-C/Swift specific *.hmap diff --git a/Diary.xcodeproj/project.pbxproj b/Diary.xcodeproj/project.pbxproj index 4abbe6fc9..55dc85f47 100644 --- a/Diary.xcodeproj/project.pbxproj +++ b/Diary.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ BABBDAE52A9F13A200D8D50B /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABBDAE42A9F13A200D8D50B /* DecodingError.swift */; }; BABBDB342AA6D05A00D8D50B /* ShareDisplayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABBDB332AA6D05A00D8D50B /* ShareDisplayable.swift */; }; BABBDB362AAD904100D8D50B /* CoreDataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABBDB352AAD904100D8D50B /* CoreDataError.swift */; }; + BAECB2CF2AB15742006B4A46 /* Key.plist in Resources */ = {isa = PBXBuildFile; fileRef = BAECB2CE2AB15742006B4A46 /* Key.plist */; }; + BAECB2D12AB157CB006B4A46 /* NetworkConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAECB2D02AB157CB006B4A46 /* NetworkConfiguration.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 */; }; @@ -54,6 +56,8 @@ BABBDAE42A9F13A200D8D50B /* DecodingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingError.swift; sourceTree = ""; }; BABBDB332AA6D05A00D8D50B /* ShareDisplayable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareDisplayable.swift; sourceTree = ""; }; BABBDB352AAD904100D8D50B /* CoreDataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataError.swift; sourceTree = ""; }; + BAECB2CE2AB15742006B4A46 /* Key.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Key.plist; sourceTree = ""; }; + BAECB2D02AB157CB006B4A46 /* NetworkConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConfiguration.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 = ""; }; @@ -99,6 +103,7 @@ isa = PBXGroup; children = ( 632F74EF2AB14D2C003E1B97 /* NetworkManager.swift */, + BAECB2D02AB157CB006B4A46 /* NetworkConfiguration.swift */, ); path = Network; sourceTree = ""; @@ -182,6 +187,7 @@ C739AE18284DF28600741E8F = { isa = PBXGroup; children = ( + BAECB2CE2AB15742006B4A46 /* Key.plist */, 63E527342A9D7EBF0000FBA6 /* .swiftlint.yml */, C739AE23284DF28600741E8F /* Diary */, C739AE22284DF28600741E8F /* Products */, @@ -277,6 +283,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + BAECB2CF2AB15742006B4A46 /* Key.plist in Resources */, 63E527352A9D7EBF0000FBA6 /* .swiftlint.yml in Resources */, C739AE34284DF28600741E8F /* LaunchScreen.storyboard in Resources */, C739AE31284DF28600741E8F /* Assets.xcassets in Resources */, @@ -342,6 +349,7 @@ BA1A55ED2A9D90810012C89D /* DateFormatter+.swift in Sources */, 63E527372A9D87660000FBA6 /* DiaryListTableViewCell.swift in Sources */, C739AE2F284DF28600741E8F /* Diary.xcdatamodeld in Sources */, + BAECB2D12AB157CB006B4A46 /* NetworkConfiguration.swift in Sources */, 63BB62B52AA182AA00524DCB /* CoreDataManager.swift in Sources */, 632F74F02AB14D2C003E1B97 /* NetworkManager.swift in Sources */, 63E527392A9D97160000FBA6 /* DiaryDetailViewContoller.swift in Sources */, diff --git a/Diary/Controller/DiaryDetailViewContoller.swift b/Diary/Controller/DiaryDetailViewContoller.swift index 3fec1384c..a9358a040 100644 --- a/Diary/Controller/DiaryDetailViewContoller.swift +++ b/Diary/Controller/DiaryDetailViewContoller.swift @@ -19,11 +19,17 @@ final class DiaryDetailViewContoller: UIViewController, AlertDisplayable, ShareD private let container = CoreDataManager.shared.persistentContainer private var diary: Diary private var isNew: Bool + private var latitude: Double? + private var longitude: Double? - init() { + init(latitude: Double?, longitude: Double?) { self.diary = CoreDataManager.shared.createDiary() self.isNew = true + self.latitude = latitude + self.longitude = longitude + super.init(nibName: nil, bundle: nil) + fetchWeather() } init(_ diary: Diary) { @@ -194,3 +200,39 @@ extension DiaryDetailViewContoller: UITextViewDelegate { diary.body = body } } + +extension DiaryDetailViewContoller { + func fetchWeather() { + guard let latitude, let longitude else { return } + + NetworkManager.shared.fetchData( + NetworkConfiguration.weatherAPI(latitude: latitude, longitude: longitude) + ) { [weak self] result in + guard let self else { return } + + switch result { + case .success(let data): + do { + let decodingData: WeatherResult = try DecodingManager.decodeData(from: data) + print(decodingData.weather.first!.main) // TODO: 마이그레이션 후 삭제 + } catch { + DispatchQueue.main.async { + let confirmAction = UIAlertAction(title: ButtonNamespace.confirm, style: .default) + self.showAlert(title: AlertNamespace.networkErrorTitle, + message: nil, + actions: [confirmAction], + preferredStyle: .alert) + } + } + case .failure: + DispatchQueue.main.async { + let confirmAction = UIAlertAction(title: ButtonNamespace.confirm, style: .default) + self.showAlert(title: AlertNamespace.networkErrorTitle, + message: nil, + actions: [confirmAction], + preferredStyle: .alert) + } + } + } + } +} diff --git a/Diary/Controller/DiaryListViewController.swift b/Diary/Controller/DiaryListViewController.swift index ba8d5b4a6..ed3afa1e5 100644 --- a/Diary/Controller/DiaryListViewController.swift +++ b/Diary/Controller/DiaryListViewController.swift @@ -1,12 +1,14 @@ // -// Diary - ViewController.swift +// Diary - DiaryListViewController.swift // Created by yagom. // Copyright © yagom. All rights reserved. // Last modified by Maxhyunm, Hamg. import UIKit +import CoreLocation final class DiaryListViewController: UIViewController { + private var locationManager = CLLocationManager() private let tableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false @@ -18,9 +20,13 @@ final class DiaryListViewController: UIViewController { private let container = CoreDataManager.shared.persistentContainer private var diaryList = [Diary]() + private var latitude: Double? + private var longitude: Double? + override func viewDidLoad() { super.viewDidLoad() - + setupLocationManager() + updateLocation() configureUI() setupNavigationBarButton() setupTableView() @@ -49,7 +55,7 @@ final class DiaryListViewController: UIViewController { private func setupNavigationBarButton() { let addDiary = UIAction(image: UIImage(systemName: "plus")) { [weak self] _ in guard let self else { return } - let createDiaryView = DiaryDetailViewContoller() + let createDiaryView = DiaryDetailViewContoller(latitude: latitude, longitude: longitude) self.navigationController?.pushViewController(createDiaryView, animated: true) } @@ -156,3 +162,24 @@ extension DiaryListViewController: UITableViewDelegate, ShareDisplayable { return UISwipeActionsConfiguration(actions: [delete, share]) } } + +extension DiaryListViewController: CLLocationManagerDelegate { + private func setupLocationManager() { + locationManager.delegate = self + locationManager.requestWhenInUseAuthorization() + locationManager.desiredAccuracy = kCLLocationAccuracyBest + } + + private func updateLocation() { + let locationStatus: [CLAuthorizationStatus] = [.authorizedAlways, .authorizedWhenInUse] + + guard locationStatus.contains(locationManager.authorizationStatus) else { return } + + locationManager.startUpdatingLocation() + + guard let location: CLLocationCoordinate2D = locationManager.location?.coordinate else { return } + + latitude = location.latitude + longitude = location.longitude + } +} diff --git a/Diary/Info.plist b/Diary/Info.plist index 0eb786dc1..4e929752e 100644 --- a/Diary/Info.plist +++ b/Diary/Info.plist @@ -2,6 +2,8 @@ + NSLocationWhenInUseUsageDescription + 위치 정보 수집을 수락합니다 UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Diary/Model/AlertNamespace.swift b/Diary/Model/AlertNamespace.swift index 2b62b5640..7c3b3ccab 100644 --- a/Diary/Model/AlertNamespace.swift +++ b/Diary/Model/AlertNamespace.swift @@ -8,4 +8,5 @@ enum AlertNamespace { static let deleteTitle = "진짜요?" static let deleteMessage = "정말로 삭제하시겠어요?" + static let networkErrorTitle = "네트워크 오류입니다" } diff --git a/Diary/Network/NetworkConfiguration.swift b/Diary/Network/NetworkConfiguration.swift new file mode 100644 index 000000000..c6609a40a --- /dev/null +++ b/Diary/Network/NetworkConfiguration.swift @@ -0,0 +1,32 @@ +// +// NetworkConfiguration.swift +// Diary +// +// Created by Maxhyunm, Hamg on 2023/09/13. +// + +import Foundation + +enum NetworkConfiguration { + case weatherAPI(latitude: Double, longitude: Double) + case weatherIcon(id: String) + + var url: String? { + switch self { + case .weatherAPI(let latitude, let longitude): + guard let apiKey = NetworkConfiguration.apiKey else { return nil } + return "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&appid=\(apiKey)" + case .weatherIcon(let id): + return "https://openweathermap.org/img/wn/\(id).png" + } + } + + static var apiKey: String? { + guard let path = Bundle.main.url(forResource: "Key", withExtension: "plist"), + let plist = NSDictionary(contentsOf: path), + let key = plist.value(forKey: "WeatherAPIKey") else { + return nil + } + return "\(key)" + } +} diff --git a/Diary/Network/NetworkManager.swift b/Diary/Network/NetworkManager.swift index 788ae42e1..2e7cf7ad3 100644 --- a/Diary/Network/NetworkManager.swift +++ b/Diary/Network/NetworkManager.swift @@ -8,11 +8,15 @@ import CoreLocation final class NetworkManager { + static let shared = NetworkManager() private var dataTask: URLSessionDataTask? - func fetchData(url: String, completionHandler: @escaping(Result) -> Void) { - guard let url = URL(string: url) else { - completionHandler(.failure(.invalidURL)) + private init() {} + + func fetchData(_ networkType: NetworkConfiguration, completionHandler: @escaping(Result) -> Void) { + guard let urlString = networkType.url, + let url = URL(string: urlString) else { + completionHandler(.failure(APIError.invalidURL)) return } @@ -35,22 +39,4 @@ final class NetworkManager { } self.dataTask?.resume() } - - func fetchLocation(location: CLLocation, _ completionHandler: @escaping(Result) -> Void) { - let urlStr = "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&appid=aaa5700cbd82d1a09e738731002f97be" - - fetchData(url: urlStr) { result in - switch result { - case .success(let data): - do { - let decodingData: WeatherResult = try DecodingManager.decodeData(from: data) - completionHandler(.success(decodingData)) - } catch { - completionHandler(.failure(APIError.decodingFail)) - } - case .failure(let error): - completionHandler(.failure(error)) - } - } - } } diff --git a/Key.plist b/Key.plist new file mode 100644 index 000000000..432f654f4 --- /dev/null +++ b/Key.plist @@ -0,0 +1,8 @@ + + + + + WeatherAPIKey + aaa5700cbd82d1a09e738731002f97be + +