티스토리 뷰

 

지난 글에서 API를 통해 Data를 받아와 Weather Model을 만들었습니다.

이번 글에서는 Model 에 있는 Data를 View에 넣어주기 위해 ViewModel을 만들겠습니다  😄

 

 

 

[SwiftUI] 날씨 어플 만들기 03 (Model 만들기, @escaping)

지난 글에서 MVVM 패턴에서 View를 구성했습니다. 이번 글에서는 Model을 만들어보겠습니다 😄 날씨 어플 만들기 01에서 구현했던 함수를 실제 앱에서 사용할 수 있게 살짝 수정해서 구현하도록 하

malchafrappuccino.tistory.com

 

 

WeatherViewModel.swift 라는 새로운 파일을 만들어줍니다

 

import SwiftUI
import Foundation

private let defaultIcon = "questionmark"
private let defaultBackgroundColor = Gradient(colors: [.blue, Color("LightBlue")])

private let iconMap = [
    "Clear" : "sun.max.fill",
    "Clouds" : "cloud.fill",
    "Drizzle" : "cloud.drizzle.fill",
    "Rain" : "cloud.rain.fill",
    "Snow" : "snowflake",
    "Thunderstorm" : "cloud.bolt.rain.fill"
]

private let backgroundColor = [
    "Clear" : Gradient(colors: [.blue, Color("LightBlue")]) ,
    "Clouds" : Gradient(colors: [.gray, Color("LightGray")]) ,
    "Drizzle" : Gradient(colors: [.gray, Color("MiddleBlue")]) ,
    "Rain" : Gradient(colors: [.gray, Color("DarkBlue")]) ,
    "Snow" : Gradient(colors: [Color("LightMagenta")]) ,
    "Thunderstorm" : Gradient(colors: [.gray, Color("LightGray"), .yellow])
]

private var getToday: String {
    let dateformatter = DateFormatter()
    dateformatter.dateFormat = "MM  /  dd"
    let todayDay = dateformatter.string(from: Date())
    return todayDay
}


public class WeatherViewModel: ObservableObject {
    @Published var cityName: String = "City"
    @Published var temperature: String = "--"
    @Published var tempMin: String = "--"
    @Published var tempMax: String = "--"
    @Published var weatherDescription: String = "--"
    @Published var weatherIcon: String = defaultIcon
    @Published var weatherBackground: Gradient = Gradient(colors: [.blue, Color("LightBlue")])
    @Published var todayDay: String = "--  /  --"
    
    public let weatherService: WeatherService
    
    public init(weatherService: WeatherService) {
        self.weatherService = weatherService
    }
    
    public func refresh() {
        weatherService.getData { weather in
            DispatchQueue.main.async {
                self.cityName = weather.city
                self.temperature = "\(weather.temperature)°C"
                self.tempMin = "\(weather.temp_min)"
                self.tempMax = "\(weather.temp_max)"
                self.weatherDescription = weather.description.capitalized
                self.weatherIcon = iconMap[weather.icon] ?? defaultIcon
                self.weatherBackground = backgroundColor[weather.icon] ?? defaultBackgroundColor
                self.todayDay = getToday
            }
        }
    }
}

 

코드 한 줄 한 줄 살펴보기

public class WeatherViewModel: ObservableObject {
    @Published var cityName: String = "City"
    @Published var temperature: String = "--"
    @Published var tempMin: String = "--"
    @Published var tempMax: String = "--"
    @Published var weatherDescription: String = "--"
    @Published var weatherIcon: String = defaultIcon
    @Published var weatherBackground: Gradient = Gradient(colors: [.blue, Color("LightBlue")])
    @Published var todayDay: String = "--  /  --"
    
    public let weatherService: WeatherService
    
    public init(weatherService: WeatherService) {
        self.weatherService = weatherService
    }
    
    public func refresh() {
        weatherService.getData { weather in
            DispatchQueue.main.async {
                self.cityName = weather.city
                self.temperature = "\(weather.temperature)°C"
                self.tempMin = "\(weather.temp_min)"
                self.tempMax = "\(weather.temp_max)"
                self.weatherDescription = weather.description.capitalized
                self.weatherIcon = iconMap[weather.icon] ?? defaultIcon
                self.weatherBackground = backgroundColor[weather.icon] ?? defaultBackgroundColor
                self.todayDay = getToday
            }
        }
    }
}

WeatherViewModel 이라는 새로운 클래스를 정의합니다. ObserableObject 라는 프로토콜을 사용해서 ViewModel 안의 인스턴스 들을 @Published로 선언합니다. 이를 통해 ViewModel에서  변경이 감지되면 View 에서 이를 감지하고 바뀔 수 있도록 합니다.

 

public let weatherService: WeatherService
    
public init(weatherService: WeatherService) {
    self.weatherService = weatherService
}

weatherService를 선언해 WeatherService를 사용할 것 입니다. init으로 weatherService를 initialize 합니다.

 

 

public func refresh() {
        weatherService.getData { weather in
            DispatchQueue.main.async {
                self.cityName = weather.city
                self.temperature = "\(weather.temperature)°C"
                self.tempMin = "\(weather.temp_min)"
                self.tempMax = "\(weather.temp_max)"
                self.weatherDescription = weather.description.capitalized
                self.weatherIcon = iconMap[weather.icon] ?? defaultIcon
                self.weatherBackground = backgroundColor[weather.icon] ?? defaultBackgroundColor
                self.todayDay = getToday
            }
        }
 }

 

refresh 라는 이름의 함수를 새로 선언하고 weatherService의 getData 함수를 호출합니다.

이 때  getData 함수는 Weather를 던지기 때문에 이 weather를 받아 ViewModel의 인스턴스에 넣어줍니다.

지난 글에서 사용했던 그림을 다시 보면

 

 

@escaping closure를 사용해 Weather를 던져줍니다.

 

 

DispatchQueue.main.async 를 사용해 네트워클 통신을 메인 쓰레드를 통해 실행합니다 (최우선 순위로)

 

icon 과 background 는 상황에 따라 정해준 Symbol과 색을 사용할 것 이기 때문에 새로운 배열을 선언합니다.

 

private let defaultIcon = "questionmark"

private let iconMap = [
    "Clear" : "sun.max.fill",
    "Clouds" : "cloud.fill",
    "Drizzle" : "cloud.drizzle.fill",
    "Rain" : "cloud.rain.fill",
    "Snow" : "snowflake",
    "Thunderstorm" : "cloud.bolt.rain.fill"
]

 

weather.icon 이 Clear 이면 self.weatherIcon (viewModel의 인스턴스) 는 sun.max.fill, Clouds면 cloud.fill ... 이렇게 6가지 경우에 따라 weaherIcon 이 결정됩니다. 이 배열을 통해 상황에 따라 알맞는 SFSymbols를 사용할 수 있게 됩니다. 6가지 경우에 해당 되지 않을 경우를 위해 default 값을 ? 로 정해주었습니다.

 

똑같이 배경화면도 바꿔주기 위한 새로운 배열을 선언해줍니다.

 

private let defaultBackgroundColor = Gradient(colors: [.blue, Color("LightBlue")])

private let backgroundColor = [
    "Clear" : Gradient(colors: [.blue, Color("LightBlue")]) ,
    "Clouds" : Gradient(colors: [.gray, Color("LightGray")]) ,
    "Drizzle" : Gradient(colors: [.gray, Color("MiddleBlue")]) ,
    "Rain" : Gradient(colors: [.gray, Color("DarkBlue")]) ,
    "Snow" : Gradient(colors: [Color("LightMagenta")]) ,
    "Thunderstorm" : Gradient(colors: [.gray, Color("LightGray"), .yellow])
]

 

Color(" ")를 처음 봤을 수도 있을 것입니다. (제가 그랬습니다 ㅎㅎ)

 

xcode에서는 assets 에서 사용자가 원하는  Custom color를 지정할 수 있습니다. 미리 저장해놓으면 매번 RGB 값을 입력할 필요 없이 변수의 이름을 사용하면 됩니다. 만약 CSS를 접해보신 분이라면 CSS 최상단에  root { } 하고 색깔을 미리 선언해서 사용했을텐데 그것과 동일하다고 생각하면 됩니다.

 

왼쪽 파일 탐색기에서 Assets.xcassets 을 누릅니다

 

 

이미지와 헷갈리지 않게 Colors라는 폴더를 새로 만들어 줍니다

 

 

왼쪽 하단에 + 버튼을 눌러주고 ColorSet을 눌러줍니다

 

 

이름은 자신이 사용하는 걸로 지어주면 됩니다. 저는 LightBlue라고 했습니다

 

 

1. Any Appearance 를 누르고 2. 우측 상단에 메뉴바를 누른뒤 3. 4번째 메뉴를 선택한다음 4. 색깔을 지정해줍니다

Dark Appearance를 통해 Dark Mode 색깔도 정해줄 수 있는데 저는 일단 Any만 했습니다.

이렇게 설정을 하면 Color("LightBlue") 를 사용해 내가 설정한 색을 쉽고 빠르게 사용할 수 있습니다.

보통은 프로젝트를 할 때 디자인에서 색을 다 정리해서 보내주기 때문에 색을 저장해놓고 사용하면 매우 편리하다고 생각합니다.

 

이렇게 5가지 색깔을 정해주었습니다. 이제 날씨에 알맞는 gradient를 보여줄 수 있게 되었습니다

 

 

DateFormatter()

마지막으로 제가 만들려고 하는 앱은 매일의 날짜를 보여줍니다. 매일 날짜를 고쳐주면 되겠지만 매우 귀찮겠죠??

Date 라는 struct를 사용하면 시간을 알 수 있습니다. 이 datevalue 값을 바로 View에 보여줄 수 없기 때문에 DateFormatter 라는 클래스를 사용해 String으로 바꿔주겠습니다.

 

private var getToday: String {
    let dateformatter = DateFormatter()
    dateformatter.dateFormat = "MM  /  dd"
    let todayDay = dateformatter.string(from: Date())
    return todayDay
}

 

Date 는 2021-09-05 14:16:43 +0000 이런 형식으로 값을 반환해줍니다

 

 

제가 지금 작성하고 있는 시간이 23시 16분인데 14시 16분으로 나옵니다. GMT 기준 시간으로 나오기 때문에 9시간 빠른 런던 시간이 나옵니다.

헉 그러면 9시간을 더해줘야 되나요?? 

그건 아닙니다. 핸드폰에서 어플이 실행되면 그 핸드폰 시간대에 맞춰서 보여주기 때문에 그런 걱정은 안해도 됩니다.

 

https://developer.apple.com/documentation/foundation/dateformatter

 

DateFormatter 의 string이라는 함수를 사용하면 String으로 뽑아낼 수 있습니다.

2021-09-05 14:16:43+0000 에서 09 (월) 와 05 (일) 만 사용하고 싶습니다.

 

월은 영어로 Month 이기 때문에 M 일은 day 이기 때문에 d 를 사용해서 뽑아줍니다.

dateformatter.dateFormat = "MM  /  dd"

만약 연도를 사용하고 싶으면 y , 요일은 E, 오전 오후는 a, 시간은 h, 분은 m, 초는 s, 밀리초는 S를 통해 뽑아낼 수 있습니다.

 

처음 날짜를 뽑아내려면 어려울 수 도 있는데요. 이럴 때 아래 사이트를 사용하면 내가 원하는 시간이나 날짜를 편하게 뽑아낼 수 있습니다.

 

 

NSDateFormatter.com - Easy Skeezy Date Formatting for Swift and Objective-C

NSDateFormatter.com Easy Skeezy Date Formatting for Swift and Objective-C ... Sunday, Sep 5, 2021 EEEE, MMM d, yyyy 09/05/2021 MM/dd/yyyy 09-05-2021 14:23 MM-dd-yyyy HH:mm Sep 5, 2:23 PM MMM d, h:mm a September 2021 MMMM yyyy Sep 5, 2021 MMM d, yyyy Sun, 5

nsdateformatter.com

 

 

 

MM dd 를 사용하면 09 05가 나오는 것을 볼 수 있습니다.

 

 

이렇게 Format String 설정을 바꿔가며 내가 먼저 원하는 값을 뽑아낼 수 있습니다.

 

 

Reference를 보면 사용법이 자세히 나와있습니다 😉

이렇게 Date와 DateFormatter을 사용해 getToday라는 변수에 오늘의 달과 일을 넣어주었습니다.

 

refresh함수에서 viewModel의 today에 getToday를 넣어줍니다.

 

 

이렇게 해서 View에서 보여줄 데이터들을 다 ViewModel에 담았습니다.

다음 글에서는 View 에 ViewModel을 담아서 화면에 데이터들을 보여주겠습니다. 

긴 글 읽어주셔서 감사합니다 ☺️

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함