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 2 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 /* WeatherInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F56A772BD169DA0079566C /* WeatherInfo.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 /* WeatherInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherInfo.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 /* WeatherInfo.swift */,
046781732BD3E48E00A0D8D6 /* WeatherView.swift */,
);
path = WeatherForecast;
sourceTree = "<group>";
Expand Down Expand Up @@ -147,12 +153,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C7F56A782BD169DA0079566C /* WeatherInfo.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
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.setTableView()

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: WeatherInfoDelegate {
func refreshNavigationTitle(title: String) {
navigationItem.title = title
}
}

extension ViewController: UITableViewDataSource {
extension ViewController: WeatherViewDelegate {
func refresh() {
let weatherInfo: WeatherInfo = WeatherInfo(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)
}

}


42 changes: 42 additions & 0 deletions WeatherForecast/WeatherForecast/WeatherInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// WeatherInfo.swift
// WeatherForecast
//
// Created by 권유정 on 2024/04/18.
//

import Foundation
import UIKit

protocol WeatherInfoDelegate {
func refreshNavigationTitle(title: String)
}

final class WeatherInfo {
Copy link
Member

Choose a reason for hiding this comment

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

Class의 이름이 모델처럼 느껴져서, JSON을 fetch하는 객체가 느껴지도록 하면 좋을 것 같아요!


private var delegate: WeatherInfoDelegate

init(delegate: WeatherInfoDelegate) {
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
}
delegate.refreshNavigationTitle(title: info.city.name)
Copy link
Member

Choose a reason for hiding this comment

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

fetchWeatherJSON와는 관련이 없는 로직인것 같은데 따로 메서드로 빼보면 어떨까요?
컴플리션 핸들러를 통해서 WeatherForecastViewController 에서도 사용가능할것 같기도 하네요.

return info
}
}
Loading