Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

날씨 앱 [STEP 1] yujeong #39

Open
wants to merge 3 commits into
base: rft_3_yujeong
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions WeatherForecast/WeatherForecast.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
046781742BD3E48E00A0D8D6 /* WeatherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046781732BD3E48E00A0D8D6 /* WeatherView.swift */; };
C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */ = {isa = PBXBuildFile; fileRef = C741F66F2B58F00500A4DDC0 /* Weather.swift */; };
C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */; };
C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743D8E2B21C38100DF0D09 /* SceneDelegate.swift */; };
Expand All @@ -16,9 +17,11 @@
C7743D992B21C38200DF0D09 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7743D972B21C38200DF0D09 /* LaunchScreen.storyboard */; };
C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */; };
C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */; };
C7F56A782BD169DA0079566C /* FetchWeatherInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F56A772BD169DA0079566C /* FetchWeatherInfo.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
046781732BD3E48E00A0D8D6 /* WeatherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherView.swift; sourceTree = "<group>"; };
C741F66F2B58F00500A4DDC0 /* Weather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weather.swift; sourceTree = "<group>"; };
C7743D892B21C38100DF0D09 /* WeatherForecast.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WeatherForecast.app; sourceTree = BUILT_PRODUCTS_DIR; };
C7743D8C2B21C38100DF0D09 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand All @@ -30,6 +33,7 @@
C7743D9A2B21C38200DF0D09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C7743DA02B21C3B400DF0D09 /* WeatherTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherTableViewCell.swift; sourceTree = "<group>"; };
C7743DA22B21CA8500DF0D09 /* WeatherDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherDetailViewController.swift; sourceTree = "<group>"; };
C7F56A772BD169DA0079566C /* FetchWeatherInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchWeatherInfo.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -72,6 +76,8 @@
C7743D952B21C38200DF0D09 /* Assets.xcassets */,
C7743D972B21C38200DF0D09 /* LaunchScreen.storyboard */,
C7743D9A2B21C38200DF0D09 /* Info.plist */,
C7F56A772BD169DA0079566C /* FetchWeatherInfo.swift */,
046781732BD3E48E00A0D8D6 /* WeatherView.swift */,
);
path = WeatherForecast;
sourceTree = "<group>";
Expand Down Expand Up @@ -147,12 +153,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C7F56A782BD169DA0079566C /* FetchWeatherInfo.swift in Sources */,
C7743DA12B21C3B400DF0D09 /* WeatherTableViewCell.swift in Sources */,
C7743D912B21C38100DF0D09 /* ViewController.swift in Sources */,
C7743D8D2B21C38100DF0D09 /* AppDelegate.swift in Sources */,
C7743DA32B21CA8600DF0D09 /* WeatherDetailViewController.swift in Sources */,
C741F6702B58F00500A4DDC0 /* Weather.swift in Sources */,
C7743D8F2B21C38100DF0D09 /* SceneDelegate.swift in Sources */,
046781742BD3E48E00A0D8D6 /* WeatherView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
7 changes: 4 additions & 3 deletions WeatherForecast/WeatherForecast/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wYg-u6-9nF">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wYg-u6-9nF">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
Expand All @@ -12,7 +13,7 @@
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="WeatherForecast" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC" customClass="WeatherView" customModule="WeatherForecast" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
Expand Down
49 changes: 49 additions & 0 deletions WeatherForecast/WeatherForecast/FetchWeatherInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// WeatherInfo.swift
// WeatherForecast
//
// Created by 권유정 on 2024/04/18.
//

import Foundation
import UIKit

protocol FetchWeatherInfoDelegate {
func refreshNavigationTitle(title: String)
}

final class FetchWeatherInfo {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터 타입에 동사형이 들어가서 함수로 생각될 수 있을것 같아요. 오히려 기존의 WaetherInfo가 더 좋은 네이밍 같습니다!


private var delegate: FetchWeatherInfoDelegate

init(delegate: FetchWeatherInfoDelegate) {
self.delegate = delegate
}

func fetchWeatherJSON() -> WeatherJSON? {

let jsonDecoder: JSONDecoder = .init()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase

guard let data = NSDataAsset(name: "weather")?.data else {
return nil
}

let info: WeatherJSON
do {
info = try jsonDecoder.decode(WeatherJSON.self, from: data)
} catch {
print(error.localizedDescription)
return nil
}
Task { @MainActor in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MainActor도 사용하셨군요 ! 👍

await refreshNavigation(with: info)
}

return info
}

@MainActor func refreshNavigation(with info: WeatherJSON) async {
delegate.refreshNavigationTitle(title: info.city.name)
}
}
154 changes: 48 additions & 106 deletions WeatherForecast/WeatherForecast/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@
import UIKit

class ViewController: UIViewController {
var tableView: UITableView!
let refreshControl: UIRefreshControl = UIRefreshControl()
var weatherJSON: WeatherJSON?
var icons: [UIImage]?
let imageChache: NSCache<NSString, UIImage> = NSCache()
let dateFormatter: DateFormatter = {
let formatter: DateFormatter = DateFormatter()
formatter.locale = .init(identifier: "ko_KR")
formatter.dateFormat = "yyyy-MM-dd(EEEEE) a HH:mm"
return formatter
}()

private let imageChache: NSCache<NSString, UIImage> = NSCache()
var tempUnit: TempUnit = .metric

override func viewDidLoad() {
Expand All @@ -40,127 +36,73 @@ extension ViewController {
refresh()
}

@objc private func refresh() {
fetchWeatherJSON()
tableView.reloadData()
refreshControl.endRefreshing()
}

private func initialSetUp() {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "화씨", image: nil, target: self, action: #selector(changeTempUnit))

layTable()
guard let weatherView: WeatherView = view as? WeatherView else {return}
weatherView.delegate = self

refreshControl.addTarget(self,
action: #selector(refresh),
for: .valueChanged)
weatherView.layTable()

tableView.refreshControl = refreshControl
tableView.register(WeatherTableViewCell.self, forCellReuseIdentifier: "WeatherCell")
tableView.dataSource = self
tableView.delegate = self
}

private func layTable() {
tableView = .init(frame: .zero, style: .plain)
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
weatherView.refreshAddTarget()

let safeArea: UILayoutGuide = view.safeAreaLayoutGuide
weatherView.configureTableView()

NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: safeArea.topAnchor),
tableView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor),
tableView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor),
tableView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor)
])
}

}

extension ViewController {
private func fetchWeatherJSON() {

let jsonDecoder: JSONDecoder = .init()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase

guard let data = NSDataAsset(name: "weather")?.data else {
return
}

let info: WeatherJSON
do {
info = try jsonDecoder.decode(WeatherJSON.self, from: data)
} catch {
print(error.localizedDescription)
return
}

weatherJSON = info
navigationItem.title = weatherJSON?.city.name
extension ViewController: FetchWeatherInfoDelegate {
func refreshNavigationTitle(title: String) {
navigationItem.title = title
}
}

extension ViewController: UITableViewDataSource {
extension ViewController: WeatherViewDelegate {
func refresh() {
let weatherInfo: FetchWeatherInfo = FetchWeatherInfo(delegate: self)
weatherJSON = weatherInfo.fetchWeatherJSON()

guard let weatherView: WeatherView = view as? WeatherView else {return}
weatherView.tableViewReloadData()
weatherView.refreshControlEndRefreshing()
}

func numberOfSections(in tableView: UITableView) -> Int {
1
func tableViewDidSelectRowAt(view: WeatherView, row: Int) {
let detailViewController: WeatherDetailViewController = WeatherDetailViewController()
detailViewController.weatherForecastInfo = weatherJSON?.weatherForecast[row]
detailViewController.cityInfo = weatherJSON?.city
detailViewController.tempUnit = tempUnit
navigationController?.show(detailViewController, sender: self)
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
weatherJSON?.weatherForecast.count ?? 0
func getWeatherForecastInfoCount() -> Int {
return weatherJSON?.weatherForecast.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "WeatherCell", for: indexPath)

guard let cell: WeatherTableViewCell = cell as? WeatherTableViewCell,
let weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row] else {
return cell
}

cell.weatherLabel.text = weatherForecastInfo.weather.main
cell.descriptionLabel.text = weatherForecastInfo.weather.description
cell.temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit.expression)"

let date: Date = Date(timeIntervalSince1970: weatherForecastInfo.dt)
cell.dateLabel.text = dateFormatter.string(from: date)

let iconName: String = weatherForecastInfo.weather.icon
let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png"

if let image = imageChache.object(forKey: urlString as NSString) {
cell.weatherIcon.image = image
return cell
}

Task {
guard let url: URL = URL(string: urlString),
let (data, _) = try? await URLSession.shared.data(from: url),
let image: UIImage = UIImage(data: data) else {
return
}

imageChache.setObject(image, forKey: urlString as NSString)

if indexPath == tableView.indexPath(for: cell) {
cell.weatherIcon.image = image
}
func getWeatherForecastInfo(row: Int) -> WeatherForecastInfo? {
return weatherJSON?.weatherForecast[row] ?? nil
}

func getTempUnit() -> String {
return tempUnit.expression
}

func convertDateToString(date: Date) -> String {
return dateFormatter.string(from: date)
}

func getImageChacheObject(urlString: String) -> UIImage? {
if let image = imageChache.object(forKey: urlString as NSString)
{
return image
}

return cell
return nil
}
}

extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)

let detailViewController: WeatherDetailViewController = WeatherDetailViewController()
detailViewController.weatherForecastInfo = weatherJSON?.weatherForecast[indexPath.row]
detailViewController.cityInfo = weatherJSON?.city
detailViewController.tempUnit = tempUnit
navigationController?.show(detailViewController, sender: self)

func setImageChacheObject(image: UIImage, urlString: String) {
imageChache.setObject(image, forKey: urlString as NSString)
}

}


11 changes: 11 additions & 0 deletions WeatherForecast/WeatherForecast/WeatherTableViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,15 @@ class WeatherTableViewCell: UITableViewCell {
weatherLabel.text = "~~~"
descriptionLabel.text = "~~~~~"
}

func setCellLabel(weatherForecastInfo: WeatherForecastInfo, tempUnit: String, dateStr: String) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

많이 길지 않아서 dateStr 라고 축약하는것 보단 dateString 이 더 좋은것 같습니다.

weatherLabel.text = weatherForecastInfo.weather.main
descriptionLabel.text = weatherForecastInfo.weather.description
temperatureLabel.text = "\(weatherForecastInfo.main.temp)\(tempUnit)"
dateLabel.text = dateStr
}

func updateWeatherIcon(with iconImage: UIImage) {
weatherIcon.image = iconImage
}
}
Loading