A SwiftUI-based weather application that fetches current conditions and a 5-day forecast, with offline resilience using SwiftData and a clean MVVM architecture.
- Current weather with themed UI based on conditions (sunny, cloudy, rainy)
- 5-day forecast summarized from 3-hour intervals
- Pull-to-refresh with graceful offline fallback and last-updated toast
- Location-based lookup with Core Location
- Persistence with SwiftData (saved locations, current weather, forecasts)
- Show offline data for saved location in case internet is not working with a toast of last updated on
- Handle different error from Location Manager and Network issue
- Unit tests using Swift Testing framework
The app follows a modular MVVM architecture with a clear separation of concerns:
- View (SwiftUI):
WeatherContentViewrenders the header, current conditions, and forecast list. It reacts toSaveLocationupdates from the view model.
- ViewModel (Combine + async services):
WeatherViewModelorchestrates fetching current weather and forecast in parallel, handles errors, manages offline fallback, and exposes state via@Publishedproperties.
- Model (Codable + SwiftData):
- Network DTOs:
CurrentWeatherResponse,ForecastResponse,ForecastItem,Coordinates,City,WeatherType. - Persistence models:
SaveLocation,CurrentWeatherModel,ForecastItemModel(SwiftData@Modeltypes). Relationships use.cascadedelete rules for consistency.
- Network DTOs:
- Services:
WeatherAPIServiceProtocoland its implementation handle network calls for current weather and forecast.LocationServiceProtocolabstracts Core Location and exposes alocationPublisherfor reactive location updates.
- The view triggers
WeatherViewModel.retry()orfetchWeather(for:). - The view model zips two publishers:
getCurrentWeatherandgetForecast. - On success, it updates SwiftData via
SaveLocation.updateSaveLocation(...)and also sets the in-memorysaveLocationfor immediate UI updates. - On failure, it shows a toast with the last updated time if cached data exists; otherwise, it surfaces an error message.
CurrentWeatherResponseandForecastItemflatten nested JSON:- Read from
main.temp,main.temp_min,main.temp_maxinto root properties (temp,tempMin,tempMax). - Extract the first
weather[0].mainand map toWeatherType. If the array is empty, default to.cloudy.
- Read from
- Encoders recreate the original nested structure to ensure symmetry, enabling encode/decode round-trips used in tests.
SaveLocationis the aggregate root for a location, containing:- Unique
id,name,coord,country - One-to-many
forecasts: [ForecastItemModel] - One-to-one
currentWeather: CurrentWeatherModel - Convenience computed values like
sortedForecastsandlastupdated
- Unique
updateSaveLocation(...)updates or inserts the aggregate and persists viaModelContext.
- Uses the Swift Testing framework (
import Testing). CurrentWeatherResponseTestscover:- Decoding valid JSON with nested
mainandweatherarray - Fallback to
.cloudywhenweatheris empty - Encoding symmetry (round-trip encode/decode)
- Failure when required keys are missing
- Decoding valid JSON with nested
- View model and services provide mock helpers for preview and unit testing (
MockWeatherAPIService,MockLocationService).
WeatherContentView:- Themed background color and image based on
weatherType - Header with large temperature and condition name
- Current day row and a list of forecast rows
.refreshableto triggerviewModel.retry()
- Themed background color and image based on
- Open the project in Xcode 15+.
- Ensure the bundle capabilities include Location and necessary network permissions (ATS if required).
- Build and run on a device or simulator with location.
- Pull to refresh to fetch current location weather.
- Weather API keys and base URLs should be configured in the
WeatherAPIServiceimplementation (not included here). Consider using build settings or a configuration plist for secrets.
MIT License unless otherwise specified.