Skip to content

Commit

Permalink
Create ViewModel entity
Browse files Browse the repository at this point in the history
Clean up code
  • Loading branch information
pawelmilek committed Mar 28, 2024
1 parent c905e67 commit d7736ee
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 90 deletions.
20 changes: 17 additions & 3 deletions SwiftyForecast.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@
E2D430F42BB4DC30003F26EE /* MotionAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E224506F2B9F3F9800F14BAA /* MotionAnimationView.swift */; };
E2D430F62BB4EBB9003F26EE /* MKCoordinateRegion+Equtable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D430F52BB4EBB9003F26EE /* MKCoordinateRegion+Equtable.swift */; };
E2D430F72BB4EBB9003F26EE /* MKCoordinateRegion+Equtable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D430F52BB4EBB9003F26EE /* MKCoordinateRegion+Equtable.swift */; };
E2D430FA2BB4F2B6003F26EE /* LocationRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D430F92BB4F2B6003F26EE /* LocationRowViewModel.swift */; };
E2D430FB2BB4F2B6003F26EE /* LocationRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D430F92BB4F2B6003F26EE /* LocationRowViewModel.swift */; };
E2D960812120F34800AE0B6E /* Geocoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D960802120F34800AE0B6E /* Geocoder.swift */; };
E2DC1458248D2F8700D1A329 /* InvalidReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DC1457248D2F8700D1A329 /* InvalidReference.swift */; };
E2DC1466248D493700D1A329 /* JSONFileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DC1465248D493700D1A329 /* JSONFileLoader.swift */; };
Expand Down Expand Up @@ -466,6 +468,7 @@
E2D430B92BB4D954003F26EE /* LocationWeatherViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationWeatherViewViewModel.swift; sourceTree = "<group>"; };
E2D430BB2BB4D954003F26EE /* LocationWeatherViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationWeatherViewModel.swift; sourceTree = "<group>"; };
E2D430F52BB4EBB9003F26EE /* MKCoordinateRegion+Equtable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MKCoordinateRegion+Equtable.swift"; sourceTree = "<group>"; };
E2D430F92BB4F2B6003F26EE /* LocationRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRowViewModel.swift; sourceTree = "<group>"; };
E2D960802120F34800AE0B6E /* Geocoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Geocoder.swift; sourceTree = "<group>"; };
E2DC1457248D2F8700D1A329 /* InvalidReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidReference.swift; sourceTree = "<group>"; };
E2DC1465248D493700D1A329 /* JSONFileLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONFileLoader.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1253,7 +1256,7 @@
children = (
E2D430B32BB4D954003F26EE /* HourlyForecastChart */,
E2D430B42BB4D954003F26EE /* LocationList.swift */,
E2D430B52BB4D954003F26EE /* LocationRow.swift */,
E2D430F82BB4F2A8003F26EE /* LocationRow */,
E2D430B62BB4D954003F26EE /* LocationSearchView.swift */,
E2D430B72BB4D954003F26EE /* LocationSearchViewController.swift */,
E2D430B82BB4D954003F26EE /* LocationWeatherView.swift */,
Expand All @@ -1270,6 +1273,15 @@
path = ViewModel;
sourceTree = "<group>";
};
E2D430F82BB4F2A8003F26EE /* LocationRow */ = {
isa = PBXGroup;
children = (
E2D430B52BB4D954003F26EE /* LocationRow.swift */,
E2D430F92BB4F2B6003F26EE /* LocationRowViewModel.swift */,
);
path = LocationRow;
sourceTree = "<group>";
};
E2DC1464248D492200D1A329 /* DataLoader */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1535,6 +1547,7 @@
E2D430C52BB4D954003F26EE /* HourlyForecastChartViewModel.swift in Sources */,
E2227E8B2ADD54D7000C3886 /* TemperatureFahrenheitFormatter.swift in Sources */,
E2227E892ADD54C7000C3886 /* TemperatureCelsiusFormatter.swift in Sources */,
E2D430FA2BB4F2B6003F26EE /* LocationRowViewModel.swift in Sources */,
E2D430BD2BB4D954003F26EE /* HourlyForecastChartDataSource.swift in Sources */,
E2D430D12BB4D954003F26EE /* LocationWeatherViewViewModel.swift in Sources */,
E2DC149524955C8D00D1A329 /* MainViewControllerViewModel.swift in Sources */,
Expand Down Expand Up @@ -1658,6 +1671,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E2D430FB2BB4F2B6003F26EE /* LocationRowViewModel.swift in Sources */,
E2D430E02BB4DB3D003F26EE /* MeasurementSystemNotification.swift in Sources */,
E2FDBD6D2B2079DF0012D7C4 /* SwiftyForecastWidget.swift in Sources */,
E2117B462B29B9F500F91ADB /* WeatherProviderDataSource.swift in Sources */,
Expand Down Expand Up @@ -1895,7 +1909,7 @@
CODE_SIGN_ENTITLEMENTS = SwiftyForecast/Resources/SwiftyForecast.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = N3HG6C68EY;
ENABLE_TESTABILITY = YES;
Expand Down Expand Up @@ -1930,7 +1944,7 @@
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = N3HG6C68EY;
INFOPLIST_FILE = "$(SRCROOT)/SwiftyForecast/Resources/Info.plist";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ struct LocationList: View {
.tint(Color(.customPrimary))
.listRowSeparator(.hidden)
ForEach(locations) { location in
LocationRow(viewModel: LocationRowViewModel(
location: location,
service: WeatherService(),
temperatureRenderer: TemperatureRenderer(),
measurementSystemNotification: MeasurementSystemNotification()
))
LocationRow(location: location)
.deleteDisabled(location.isUserLocation)
.onTapGesture {
onSelectRow(location)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// LocationRow.swift
// SwiftyForecast
//
// Created by Pawel Milek on 10/24/23.
// Copyright © 2023 Pawel Milek. All rights reserved.
//

import SwiftUI
import MapKit

struct LocationRow: View {
@StateObject var viewModel: LocationRowViewModel

init(location: LocationModel) {
_viewModel = StateObject(wrappedValue: LocationRowViewModel(
location: location,
service: WeatherService(),
temperatureRenderer: TemperatureRenderer(),
measurementSystemNotification: MeasurementSystemNotification()
))
}

var body: some View {
VStack(alignment: .leading, spacing: 5) {
headerView
mapView
}
.padding(8)
.frame(minHeight: 145, maxHeight: 145)
.fixedSize(horizontal: false, vertical: true)
.listRowInsets(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12))
}
}

private extension LocationRow {
var headerView: some View {
HStack {
VStack(alignment: .leading, spacing: 0) {
Text(viewModel.localTime)
.font(Style.LocationRow.timeFont)
.foregroundStyle(.customPrimary)
Text(viewModel.name)
.font(Style.LocationRow.nameFont)
.foregroundStyle(Style.LocationRow.nameColor)
}
.frame(maxWidth: .infinity, alignment: .leading)
Text(viewModel.temperature)
.font(Style.LocationRow.tempFont)
.foregroundStyle(Style.LocationRow.tempColor)
.overlay {
ProgressView()
.tint(.customPrimary)
.opacity(viewModel.isLoading ? 1 : 0)
.animation(.easeOut, value: viewModel.isLoading)
}
}
}

var mapView: some View {
Map(position: $viewModel.position, interactionModes: []) {
if let center = viewModel.position.region?.center {
Marker(viewModel.name, coordinate: center)
.tint(.customPrimary)
}
}
.cornerRadius(Style.LocationRow.cornerRadius)
.clipShape(
RoundedRectangle(
cornerRadius: Style.LocationRow.cornerRadius,
style: .continuous
)
.inset(by: 2.5)
)
.shadow(
color: Style.LocationRow.shadowColor,
radius: Style.LocationRow.shadowRadius,
x: Style.LocationRow.shadowOffset.x,
y: Style.LocationRow.shadowOffset.y
)
}
}

#Preview(traits: .sizeThatFitsLayout) {
LocationRow(location: LocationModel.examples.first!)
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
//
// LocationRow.swift
// LocationRowViewModel.swift
// SwiftyForecast
//
// Created by Pawel Milek on 10/24/23.
// Copyright © 2023 Pawel Milek. All rights reserved.
// Created by Pawel Milek on 3/27/24.
// Copyright © 2024 Pawel Milek. All rights reserved.
//

import SwiftUI
import Foundation
import MapKit
import Combine
import SwiftUI

@MainActor
final class LocationRowViewModel: ObservableObject {
@Published var position: MapCameraPosition = .automatic
@Published private(set) var error: Error?
@Published private(set) var isLoading = false
@Published private(set) var locationName = ""
@Published private(set) var temperature = ""
@Published private(set) var localTime = ""
@Published private(set) var name = ""
@Published private(set) var region = MKCoordinateRegion()
@Published private(set) var temperatureValue: TemperatureValue?
@Published private(set) var location: LocationModel?

Expand Down Expand Up @@ -82,7 +83,8 @@ final class LocationRowViewModel: ObservableObject {
)

let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
region = MKCoordinateRegion(center: annotation.coordinate, span: span)
let region = MKCoordinateRegion(center: annotation.coordinate, span: span)
position = .region(region)
loadData(at: location)
}
.store(in: &cancellables)
Expand Down Expand Up @@ -114,78 +116,3 @@ final class LocationRowViewModel: ObservableObject {
temperature = rendered.currentFormatted
}
}

struct LocationRow: View {
@State private var position: MapCameraPosition = .automatic
@ObservedObject var viewModel: LocationRowViewModel

var body: some View {
VStack(alignment: .leading, spacing: 5) {
headerView
mapView
.clipShape(
RoundedRectangle(
cornerRadius: Style.LocationRow.cornerRadius,
style: .continuous
)
.inset(by: 2.5)
)
.shadow(
color: Style.LocationRow.shadowColor,
radius: Style.LocationRow.shadowRadius,
x: Style.LocationRow.shadowOffset.x,
y: Style.LocationRow.shadowOffset.y
)
}
.padding(8)
.frame(minHeight: 145, maxHeight: 145)
.fixedSize(horizontal: false, vertical: true)
.listRowInsets(EdgeInsets(top: 8, leading: 12, bottom: 8, trailing: 12))
.onChange(of: viewModel.region, initial: false) {
position = .region(viewModel.region)
}
}
}

private extension LocationRow {

var headerView: some View {
HStack {
VStack(alignment: .leading, spacing: 0) {
Text(viewModel.localTime)
.font(Style.LocationRow.timeFont)
.foregroundStyle(.customPrimary)
Text(viewModel.name)
.font(Style.LocationRow.nameFont)
.foregroundStyle(Style.LocationRow.nameColor)
}
.frame(maxWidth: .infinity, alignment: .leading)
Text(viewModel.temperature)
.font(Style.LocationRow.tempFont)
.foregroundStyle(Style.LocationRow.tempColor)
.overlay {
ProgressView()
.tint(.customPrimary)
.opacity(viewModel.isLoading ? 1 : 0)
.animation(.easeOut, value: viewModel.isLoading)
}
}
}

var mapView: some View {
Map(position: $position, interactionModes: []) {
Marker(viewModel.name, coordinate: viewModel.region.center)
.tint(.customPrimary)
}
.cornerRadius(Style.LocationRow.cornerRadius)
}
}

#Preview(traits: .sizeThatFitsLayout) {
LocationRow(viewModel: LocationRowViewModel(
location: LocationModel.examples.first!,
service: WeatherService(),
temperatureRenderer: TemperatureRenderer(),
measurementSystemNotification: MeasurementSystemNotification()
))
}

0 comments on commit d7736ee

Please sign in to comment.