diff --git a/README.md b/README.md
index e0926fc..00e0a78 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
## What It Is:
* An iOS Weather App that uses Apple's WeatherKit API
-
+
+---
+
### [App Store Link](https://apps.apple.com/us/app/stormylaunch-weather-app/id6444372213)
## Background Info:
@@ -28,8 +30,9 @@ Minimum iOS: 16.0
Dependencies: None
```
-The iOS app is built mostly in UIKit, with some occasionsal SwiftUI elements
-The WatchOS app is built entirelty in SwiftUI
+The iOS app is built mostly in UIKit, with some occasionsal SwiftUI elements.
+Also has support for iPad.
+The WatchOS app is built entirelty in SwiftUI.
## Contributing
diff --git a/programmatic-ui-weather-app/Delegates/SceneDelegate.swift b/programmatic-ui-weather-app/Delegates/SceneDelegate.swift
index 4bdb001..946563f 100644
--- a/programmatic-ui-weather-app/Delegates/SceneDelegate.swift
+++ b/programmatic-ui-weather-app/Delegates/SceneDelegate.swift
@@ -19,10 +19,27 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: UIScreen.main.bounds)
- let home = TabBarViewController()
- self.window?.rootViewController = TabBarViewController()
- window?.windowScene = windowScene
- window?.makeKeyAndVisible()
+
+ switch UIDevice.current.userInterfaceIdiom {
+ case .phone:
+ // It's an iPhone
+ print("iphone")
+ let home = TabBarViewController()
+ self.window?.rootViewController = TabBarViewController()
+ window?.windowScene = windowScene
+ window?.makeKeyAndVisible()
+ case .pad:
+ // It's an iPad (or macOS Catalyst)
+ print("ipad")
+ let home = iPadMainViewController()
+ self.window?.rootViewController = iPadMainViewController()
+ window?.windowScene = windowScene
+ window?.makeKeyAndVisible()
+
+ @unknown default:
+ // Uh, oh! What could it be?
+ print("oops, unknown device")
+ }
}
func sceneDidDisconnect(_ scene: UIScene) {
diff --git a/programmatic-ui-weather-app/Info.plist b/programmatic-ui-weather-app/Info.plist
index 1a0945a..39258ce 100644
--- a/programmatic-ui-weather-app/Info.plist
+++ b/programmatic-ui-weather-app/Info.plist
@@ -2,6 +2,10 @@
+ BGTaskSchedulerPermittedIdentifiers
+
+ com.ES.refresh
+
UIAppFonts
SpaceX.ttf
@@ -31,9 +35,5 @@
fetch
- BGTaskSchedulerPermittedIdentifiers
-
- com.ES.refresh
-
diff --git a/programmatic-ui-weather-app/Utils/WeatherKitCallUtil.swift b/programmatic-ui-weather-app/Utils/WeatherKitCallUtil.swift
index 5208dd2..66ebe05 100644
--- a/programmatic-ui-weather-app/Utils/WeatherKitCallUtil.swift
+++ b/programmatic-ui-weather-app/Utils/WeatherKitCallUtil.swift
@@ -53,6 +53,7 @@ extension MainViewController {
MF0.numberFormatter.maximumFractionDigits = 3
let uv = result.0.uvIndex.value
+ let uvCategory = result.0.uvIndex.category.description
let windSpeed = MF0.string(from: result.0.wind.speed)
print(windSpeed)
let windDirection = result.0.wind.compassDirection
@@ -143,6 +144,7 @@ extension MainViewController {
WeatherKitData.TempMax = tempMax
WeatherKitData.TempMin = tempMin
WeatherKitData.UV = uv
+ WeatherKitData.UVCategory = uvCategory
WeatherKitData.WindSpeed = windSpeed
WeatherKitData.WindDirection = "\(windDirection)"
WeatherKitData.Symbol = symbol
@@ -177,6 +179,7 @@ extension MainViewController {
}
}
//@MainActor runs after getweather Task completes
+ //@MainActor ensures all code runs on main dispatch thread
@MainActor
private func updateLabelsAfterAwait() {
ForecastListVC().forecastTableView.reloadData()
@@ -187,6 +190,7 @@ extension MainViewController {
let date = WeatherKitData.SunsetDate
let interval = WeatherKitData.SunsetDate.timeIntervalSinceReferenceDate - WeatherKitData.SunriseDate.timeIntervalSinceReferenceDate
+ print("interval time: \(interval)")
let rocketTimer = Timer(fireAt: date, interval: interval, target: self, selector: #selector(AnimateRocket), userInfo: nil, repeats: false)
RunLoop.main.add(rocketTimer, forMode: RunLoop.Mode.common)
diff --git a/programmatic-ui-weather-app/View Controllers/MainVC/HourlyCollectionViewCell/HourlyCollectionViewCell.swift b/programmatic-ui-weather-app/View Controllers/MainVC/HourlyCollectionViewCell/HourlyCollectionViewCell.swift
index 55c8e50..8e23c96 100644
--- a/programmatic-ui-weather-app/View Controllers/MainVC/HourlyCollectionViewCell/HourlyCollectionViewCell.swift
+++ b/programmatic-ui-weather-app/View Controllers/MainVC/HourlyCollectionViewCell/HourlyCollectionViewCell.swift
@@ -29,6 +29,8 @@ class CustomCell: UICollectionViewCell {
private func setupUI() {
// bigger top stackview
topStack = UIStackView()
+// topStack.applyBlurEffect(cornerRadius: 15)
+// topStack.layer.cornerRadius = 15
// individual stackview
stackView = UIStackView()
diff --git a/programmatic-ui-weather-app/View Controllers/MainVC/MainViewController.swift b/programmatic-ui-weather-app/View Controllers/MainVC/iPhoneMainViewController.swift
similarity index 86%
rename from programmatic-ui-weather-app/View Controllers/MainVC/MainViewController.swift
rename to programmatic-ui-weather-app/View Controllers/MainVC/iPhoneMainViewController.swift
index dbafa71..ed34e2b 100644
--- a/programmatic-ui-weather-app/View Controllers/MainVC/MainViewController.swift
+++ b/programmatic-ui-weather-app/View Controllers/MainVC/iPhoneMainViewController.swift
@@ -40,6 +40,34 @@ class MainViewController: UIViewController, CLLocationManagerDelegate, UIScrollV
let windTitleLabel = UILabel()
let windLabel = UILabel()
+ // uvView variables
+ let uvView = UIView()
+ let uvTitleLabel = UILabel()
+ private let uvProgressView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.layer.cornerRadius = 4
+ view.clipsToBounds = true
+ view.backgroundColor = UIColor.systemGray2.withAlphaComponent(0.3) // Add this line
+ return view
+ }()
+
+ private let uvGradientLayer: CAGradientLayer = {
+ let layer = CAGradientLayer()
+ layer.colors = [
+ UIColor.systemGreen.cgColor,
+ UIColor.systemYellow.cgColor,
+ UIColor.systemOrange.cgColor,
+ UIColor.systemRed.cgColor,
+ UIColor.systemPurple.cgColor
+ ]
+ layer.startPoint = CGPoint(x: 0, y: 0.5)
+ layer.endPoint = CGPoint(x: 1, y: 0.5)
+ layer.cornerRadius = 2 // Add this line to match the view's corner radius
+ return layer
+ }()
+
+
// collection view for hourly forecast
let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init()) // need to have frame and layout for UICollectionView
@@ -50,7 +78,6 @@ class MainViewController: UIViewController, CLLocationManagerDelegate, UIScrollV
//Creates view for cloud at top
let uiView = UIView()
-
override func viewDidLoad() {
super.viewDidLoad()
@@ -209,7 +236,7 @@ class MainViewController: UIViewController, CLLocationManagerDelegate, UIScrollV
windView.layer.cornerRadius = 20
windView.isUserInteractionEnabled = true
windView.addGestureRecognizer(windTapGesture)
-// windView.applyBlurEffect(.systemUltraThinMaterialLight, cornerRadius: 20)
+// windView.applyBlurEffect(.systemUltraThinMaterialDark, cornerRadius: 20)
windTitleLabel.translatesAutoresizingMaskIntoConstraints = false
windTitleLabel.text = "Wind"
@@ -219,6 +246,15 @@ class MainViewController: UIViewController, CLLocationManagerDelegate, UIScrollV
windLabel.text = "--MPH"
windLabel.font = .preferredFont(forTextStyle: .title1)
+ uvView.translatesAutoresizingMaskIntoConstraints = false
+ uvView.backgroundColor = cyanColor
+ uvView.layer.cornerRadius = 20
+// uvView.applyBlurEffect(.systemUltraThinMaterialDark, cornerRadius: 20)
+
+ uvTitleLabel.translatesAutoresizingMaskIntoConstraints = false
+ uvTitleLabel.text = "UV"
+ uvTitleLabel.font = .preferredFont(forTextStyle: .body)
+
//Sets settings for refreshControl
refreshControl.translatesAutoresizingMaskIntoConstraints = false
refreshControl.attributedTitle = NSAttributedString("Fetching Weather")
@@ -236,6 +272,7 @@ class MainViewController: UIViewController, CLLocationManagerDelegate, UIScrollV
scrollView.addSubview(precipitationView)
scrollView.addSubview(hourlyForecastView)
scrollView.addSubview(windView)
+ scrollView.addSubview(uvView)
scrollView.addSubview(refreshControl)
//Adds elements into precipitationView
@@ -246,6 +283,12 @@ class MainViewController: UIViewController, CLLocationManagerDelegate, UIScrollV
windView.addSubview(windTitleLabel)
windView.addSubview(windLabel)
+ // Adds elements into uvView
+ uvView.addSubview(uvTitleLabel)
+ uvView.addSubview(uvProgressView)
+
+ uvProgressView.layer.addSublayer(uvGradientLayer)
+
//Adds elements into hourlyForecastView
hourlyForecastView.addSubview(hourlyForecastTitleLabel)
hourlyForecastView.addSubview(collectionView)
@@ -316,6 +359,19 @@ class MainViewController: UIViewController, CLLocationManagerDelegate, UIScrollV
//windLabel constraints
windLabel.centerYAnchor.constraint(equalTo: windView.centerYAnchor),
windLabel.centerXAnchor.constraint(equalTo: windView.centerXAnchor),
+ // uvView contraints
+ uvView.topAnchor.constraint(equalTo: windView.bottomAnchor, constant: 20),
+ uvView.leadingAnchor.constraint(equalTo: windView.leadingAnchor),
+ uvView.trailingAnchor.constraint(equalTo: windView.trailingAnchor),
+ uvView.heightAnchor.constraint(equalToConstant: 100),
+ // uvTitleLabel constraints
+ uvTitleLabel.topAnchor.constraint(equalTo: uvView.topAnchor, constant: 10),
+ uvTitleLabel.leadingAnchor.constraint(equalTo: uvView.leadingAnchor, constant: 10),
+ // uvProgressView constraints
+ uvProgressView.topAnchor.constraint(equalTo: uvTitleLabel.bottomAnchor, constant: 10),
+ uvProgressView.heightAnchor.constraint(equalToConstant: 6),
+ uvProgressView.leadingAnchor.constraint(equalTo: uvView.leadingAnchor, constant: 10),
+ uvProgressView.trailingAnchor.constraint(equalTo: uvView.trailingAnchor, constant: -10)
])//Constraint Array
}//Layout func
}//MainViewController class
@@ -379,6 +435,7 @@ extension MainViewController {
self.todayTempLabel.text = "H:\(WeatherKitData.TempMax) L:\(WeatherKitData.TempMin)"
self.windLabel.text = "\(WeatherKitData.WindSpeed)"
self.precipitationLabel.text = "\(WeatherKitData.PrecipitationChance)% Chance"
+ self.updateUVIndex(WeatherKitData.UV)
DateConverter().timeArrayMaker()
}
@@ -407,6 +464,34 @@ extension MainViewController {
fetchFromReload()
}
+ // Stuff so that uvGradientLayer frame works
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+ // Update gradient layer frame
+ uvGradientLayer.frame = uvProgressView.bounds
+ }
+
+ // Function to update the UVIndex
+ func updateUVIndex(_ value: Int) {
+ print("skibb: \(WeatherKitData.UVCategory)")
+ // Create a mask layer to achieve the progress bar effect
+ let maskLayer = CALayer()
+ let clampedValue = min(max(value, 0), 11)
+ let percentage = CGFloat(clampedValue) / 11.0
+
+ // The gradient layer should always be full width
+ uvGradientLayer.frame = uvProgressView.bounds
+
+ // Create a mask that only shows a portion of the gradient
+ maskLayer.frame = CGRect(x: 0, y: 0,
+ width: uvProgressView.bounds.width * percentage,
+ height: uvProgressView.bounds.height)
+ maskLayer.backgroundColor = UIColor.white.cgColor
+
+ // Apply the mask to the gradient layer
+ uvGradientLayer.mask = maskLayer
+ }
+
//Creates a function for running fetchWeather from reload func
func fetchFromReload() {
DispatchQueue.main.async {
@@ -548,10 +633,12 @@ extension MainViewController {
}
}
+ // number of hourly cells
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
+ // cell creator
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
@@ -565,6 +652,7 @@ extension MainViewController {
}
} else { cell.weatherIcon.image = UIImage(systemName: "questionmark")}
+ // displays them only if more than 6 items in HourluForecast
if WeatherKitData.HourlyForecast.count > 6 {
cell.tempLabel.text = "\(Int((round(WeatherKitData.HourlyForecast[indexPath.row])*100)/100))˚"
} else { cell.tempLabel.text = "--" }
@@ -575,4 +663,5 @@ extension MainViewController {
return cell
}
+
}
diff --git a/programmatic-ui-weather-app/WeatherStruct/WeatherKitData.swift b/programmatic-ui-weather-app/WeatherStruct/WeatherKitData.swift
index 3dcf911..da262dd 100644
--- a/programmatic-ui-weather-app/WeatherStruct/WeatherKitData.swift
+++ b/programmatic-ui-weather-app/WeatherStruct/WeatherKitData.swift
@@ -39,8 +39,10 @@ struct WeatherKitData: Codable{
static var TempFeels = 0
static var UV = 0
+ static var UVCategory = ""
static var WindDirection = ""
+ static var WindDirectionAngle = 0.0
static var WindGusts = [Measurement]()
static var WindSpeed = ""
static var WindSpeedForecast = [0.0]
diff --git a/programmatic-ui-weather-app/iPad Version/Rocket Utils/LaunchRocket.swift b/programmatic-ui-weather-app/iPad Version/Rocket Utils/LaunchRocket.swift
new file mode 100644
index 0000000..ba72de4
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/Rocket Utils/LaunchRocket.swift
@@ -0,0 +1,26 @@
+//
+// LaunchRocket.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/4/23.
+//
+import UIKit
+import Foundation
+
+
+extension iPadMainViewController {
+ @objc func launchRocket() {
+ // rocketView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
+ UIImageView.animate(withDuration: 4.4, animations: {
+ [weak self] in
+ self!.rocketView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
+ self!.rocketView.frame.origin.y -= 1000
+ }) { (done) in
+ UIImageView.animate(withDuration: 2.0, delay: 8.0, options: [.curveEaseOut], animations: {
+ [weak self] in
+ self!.rocketView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
+ self!.rocketView.frame.origin.y += 1000
+ })
+ }
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/HumidityStackView.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/HumidityStackView.swift
new file mode 100644
index 0000000..ecbc947
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/HumidityStackView.swift
@@ -0,0 +1,83 @@
+///
+// iPadHumidityStack.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/5/23.
+//
+
+import UIKit
+
+
+class iPadHumidityStack: UIView {
+ // MARK: START: stuff needed for UIView
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+// createHumidityView()
+ setupUI()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ }
+ // MARK: END
+
+
+ // Topmost stack
+ let humidityView: UIStackView = {
+ let stack = UIStackView()
+ stack.translatesAutoresizingMaskIntoConstraints = false
+ stack.axis = .vertical
+ stack.spacing = 15
+ stack.backgroundColor = cyanColor
+ stack.layer.cornerRadius = 15
+ stack.isLayoutMarginsRelativeArrangement = true
+ stack.layoutMargins = UIEdgeInsets(top: 10, left: 0, bottom: 40, right: 0)
+ return stack
+ }()
+
+ let humidityTitleLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Humidity"
+ label.textAlignment = .center
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = .boldSystemFont(ofSize: 18.0)
+ return label
+ }()
+
+ let humidityLabel: UILabel = {
+ let label = UILabel()
+ label.text = "humidity"
+ label.textAlignment = .center
+ label.translatesAutoresizingMaskIntoConstraints = false
+ return label
+ }()
+
+ let dewPointLabel: UILabel = {
+ let label = UILabel()
+ label.text = "dew point"
+ label.textAlignment = .center
+ label.translatesAutoresizingMaskIntoConstraints = false
+ return label
+ }()
+
+ // setsup the UI for the humidityStack
+ func setupUI() {
+
+ humidityLabel.text = "\(WeatherKitData.Humidity)%"
+ humidityView.addArrangedSubview(humidityTitleLabel)
+ humidityView.addArrangedSubview(humidityLabel)
+
+ addSubview(humidityView)
+
+ // Setup constraints for humidityView to fill iPadHumidityStack
+ NSLayoutConstraint.activate([
+ humidityView.topAnchor.constraint(equalTo: topAnchor),
+ humidityView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ humidityView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ humidityView.bottomAnchor.constraint(equalTo: bottomAnchor)
+ ])
+ }
+ func updateHumidityLabels(_ value: Int) {
+ humidityLabel.text = "\(value)%"
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/MainScrollView.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/MainScrollView.swift
new file mode 100644
index 0000000..19c5e65
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/MainScrollView.swift
@@ -0,0 +1,20 @@
+//
+// MainScrollView.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 12/17/24.
+//
+
+import Foundation
+import UIKit
+
+extension iPadMainViewController {
+ func setupMainScrollView() {
+ //mainScrollView settings
+ mainScrollView.translatesAutoresizingMaskIntoConstraints = false
+ mainScrollView.contentSize = CGSize(width: 200, height: 300)
+ mainScrollView.alwaysBounceVertical = true
+ mainScrollView.isScrollEnabled = true
+ mainScrollView.alwaysBounceHorizontal = false
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/PrecipitationView.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/PrecipitationView.swift
new file mode 100644
index 0000000..9535df5
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/PrecipitationView.swift
@@ -0,0 +1,108 @@
+//
+// PrecipitationView.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/14/23.
+//
+
+import Foundation
+import UIKit
+
+//extension iPadMainViewController {
+// func createPrecipitationView() {
+// precipitationView.translatesAutoresizingMaskIntoConstraints = false
+// precipitationView.layer.cornerRadius = 15
+// precipitationView.axis = .vertical
+// precipitationView.backgroundColor = cyanColor
+// precipitationView.isLayoutMarginsRelativeArrangement = true
+// precipitationView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 30, right: 0)
+// precipitationView.spacing = 20
+//
+// let PrecipitationViewTitleLabel: UILabel = {
+// let label = UILabel()
+// label.text = "Precipitation"
+// label.textAlignment = .center
+// label.font = .boldSystemFont(ofSize: 18.0)
+// return label
+// }()
+//
+// let PrecipitationChanceLabel: UILabel = {
+// let label = UILabel()
+// label.text = "--"
+// label.textAlignment = .center
+// return label
+// }()
+//
+// PrecipitationChanceLabel.text = "\(WeatherKitData.PrecipitationChance)% Chance"
+//
+// precipitationView.addArrangedSubview(PrecipitationViewTitleLabel)
+// precipitationView.addArrangedSubview(PrecipitationChanceLabel)
+// }
+//}
+
+
+class iPadPrecipitationView: UIView {
+ // MARK: - Properties
+ private let precipitationStackView: UIStackView = {
+ let stack = UIStackView()
+ stack.translatesAutoresizingMaskIntoConstraints = false
+ stack.layer.cornerRadius = 15
+ stack.axis = .vertical
+ stack.backgroundColor = cyanColor
+ stack.isLayoutMarginsRelativeArrangement = true
+ stack.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 30, right: 0)
+ stack.spacing = 20
+ return stack
+ }()
+
+ private let PrecipitationViewTitleLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Precipitation"
+ label.textAlignment = .center
+ label.font = .boldSystemFont(ofSize: 18.0)
+ return label
+ }()
+
+ private let PrecipitationChanceLabel: UILabel = {
+ let label = UILabel()
+ label.text = "--"
+ label.textAlignment = .center
+ return label
+ }()
+
+ // MARK: - Init
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupUI()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ setupUI()
+ }
+
+ // MARK: - Setup
+ private func setupUI() {
+ // Add labels to stack
+ precipitationStackView.addArrangedSubview(PrecipitationViewTitleLabel)
+ precipitationStackView.addArrangedSubview(PrecipitationChanceLabel)
+
+ // Add stack to view
+ addSubview(precipitationStackView)
+
+ // Setup constraints
+ NSLayoutConstraint.activate([
+ precipitationStackView.topAnchor.constraint(equalTo: topAnchor),
+ precipitationStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ precipitationStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ precipitationStackView.bottomAnchor.constraint(equalTo: bottomAnchor)
+ ])
+
+ updatePrecipitationLabel(WeatherKitData.PrecipitationChance)
+ }
+
+ // MARK: - Public Methods
+ func updatePrecipitationLabel(_ value: Int) {
+ PrecipitationChanceLabel.text = "\(value)% Chance"
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/PressureView.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/PressureView.swift
new file mode 100644
index 0000000..945e3de
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/PressureView.swift
@@ -0,0 +1,39 @@
+//
+// PressureView.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/14/23.
+//
+
+import Foundation
+import UIKit
+
+extension iPadMainViewController {
+ func createPressureView() {
+ pressureView.translatesAutoresizingMaskIntoConstraints = false
+ pressureView.layer.cornerRadius = 15
+ pressureView.axis = .vertical
+ pressureView.backgroundColor = cyanColor
+ pressureView.isLayoutMarginsRelativeArrangement = true
+ pressureView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 40, right: 0)
+ pressureView.spacing = 20
+
+ let PressureViewTitleLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Pressure"
+ label.textAlignment = .center
+ label.font = .boldSystemFont(ofSize: 18.0)
+ return label
+ }()
+
+ let PressureLabel: UILabel = {
+ let label = UILabel()
+ label.text = "--"
+ label.textAlignment = .center
+ return label
+ }()
+
+ pressureView.addArrangedSubview(PressureViewTitleLabel)
+ pressureView.addArrangedSubview(PressureLabel)
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/SunsetSunriseView.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/SunsetSunriseView.swift
new file mode 100644
index 0000000..fef5988
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/SunsetSunriseView.swift
@@ -0,0 +1,132 @@
+//
+// File.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 12/13/23.
+//
+
+import Foundation
+import UIKit
+
+//extension iPadMainViewController {
+// func createSunsetView() {
+// sunsetView.translatesAutoresizingMaskIntoConstraints = false
+// sunsetView.layer.cornerRadius = 15
+// sunsetView.axis = .vertical
+// sunsetView.spacing = 10
+// sunsetView.backgroundColor = cyanColor
+// sunsetView.isLayoutMarginsRelativeArrangement = true
+// sunsetView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
+//
+//
+// let sunsetViewTitleLabel: UILabel = {
+// let label = UILabel()
+// label.text = "Sunset & Sunrise"
+// label.textAlignment = .center
+// label.font = .boldSystemFont(ofSize: 18.0)
+// return label
+// }()
+// let sunriseLabel: UILabel = {
+// let label = UILabel()
+// label.text = "Sunrise: --"
+// label.textAlignment = .center
+// return label
+// }()
+// let sunsetLabel: UILabel = {
+// let label = UILabel()
+// label.text = "Sunset: --"
+// label.textAlignment = .center
+// return label
+// }()
+// let AMPMFormatter = DateFormatter()
+// AMPMFormatter.dateFormat = "hh:mm a"
+//
+// sunriseLabel.text = "Sunrise: \(AMPMFormatter.string(from: WeatherKitData.Sunrise))"
+// sunsetLabel.text = "Sunset: \(AMPMFormatter.string(from: WeatherKitData.Sunset))"
+//
+// sunsetView.addArrangedSubview(sunsetViewTitleLabel)
+// sunsetView.addArrangedSubview(sunriseLabel)
+// sunsetView.addArrangedSubview(sunsetLabel)
+// }
+//}
+
+class iPadSunsetView: UIView {
+ // MARK: - Properties
+ private let AMPMFormatter: DateFormatter = {
+ let formatter = DateFormatter()
+ formatter.dateFormat = "hh:mm a"
+ return formatter
+ }()
+
+ let sunsetView: UIStackView = {
+ let stack = UIStackView()
+ stack.translatesAutoresizingMaskIntoConstraints = false
+ stack.layer.cornerRadius = 15
+ stack.axis = .vertical
+ stack.spacing = 10
+ stack.backgroundColor = cyanColor
+ stack.isLayoutMarginsRelativeArrangement = true
+ stack.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
+ return stack
+ }()
+
+ let sunsetViewTitleLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Sunset & Sunrise"
+ label.textAlignment = .center
+ label.font = .boldSystemFont(ofSize: 18.0)
+ return label
+ }()
+
+ let sunriseLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Sunrise: --"
+ label.textAlignment = .center
+ return label
+ }()
+
+ let sunsetLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Sunset: --"
+ label.textAlignment = .center
+ return label
+ }()
+
+ // MARK: - Init
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupUI()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ setupUI()
+ }
+
+ // MARK: - Setup
+ func setupUI() {
+ // Add subviews to stack
+ sunsetView.addArrangedSubview(sunsetViewTitleLabel)
+ sunsetView.addArrangedSubview(sunriseLabel)
+ sunsetView.addArrangedSubview(sunsetLabel)
+
+ // Add stack to view
+ addSubview(sunsetView)
+
+ // Setup constraints for sunsetView to fill SunsetView
+ NSLayoutConstraint.activate([
+ sunsetView.topAnchor.constraint(equalTo: topAnchor),
+ sunsetView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ sunsetView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ sunsetView.bottomAnchor.constraint(equalTo: bottomAnchor)
+ ])
+
+ updateSunsetLabels(WeatherKitData.Sunrise, WeatherKitData.Sunset)
+ }
+
+ // MARK: - Public Methods
+ func updateSunsetLabels(_ value1: Date, _ value2: Date) {
+ sunriseLabel.text = "Sunrise: \(AMPMFormatter.string(from: value1))"
+ sunsetLabel.text = "Sunset: \(AMPMFormatter.string(from: value2))"
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/UVView.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/UVView.swift
new file mode 100644
index 0000000..84da8ff
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/UVView.swift
@@ -0,0 +1,209 @@
+//
+// testExt.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/13/23.
+//
+
+import Foundation
+import UIKit
+
+//extension iPadMainViewController {
+//
+// func createUVView() {
+// UVView.translatesAutoresizingMaskIntoConstraints = false
+// UVView.layer.cornerRadius = 15
+// UVView.axis = .vertical
+// UVView.backgroundColor = cyanColor
+//// UVView.isLayoutMarginsRelativeArrangement = true
+// UVView.isLayoutMarginsRelativeArrangement = true
+// UVView.layoutMargins = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
+// UVView.spacing = 20
+//
+// let UVViewTitleLabel: UILabel = {
+// let label = UILabel()
+// label.text = "UV Index"
+// label.textAlignment = .center
+// label.font = .boldSystemFont(ofSize: 18.0)
+// return label
+// }()
+//
+// let UVLabel: UILabel = {
+// let label = UILabel()
+// label.text = "--"
+// label.textAlignment = .center
+// return label
+// }()
+//
+// uvProgressView.layer.addSublayer(uvGradientLayer)
+// NSLayoutConstraint.activate([
+// uvProgressView.heightAnchor.constraint(equalToConstant: 6),
+//// uvProgressView.leadingAnchor.constraint(equalTo: UVView.leadingAnchor, constant: 10),
+//// uvProgressView.trailingAnchor.constraint(equalTo: UVView.trailingAnchor, constant: -10)
+// ])
+//
+// UVLabel.text = "\(WeatherKitData.UV)"
+// UVView.addArrangedSubview(UVViewTitleLabel)
+// UVView.addArrangedSubview(UVLabel)
+// UVView.addArrangedSubview(uvProgressView)
+// }
+//
+// // Stuff so that uvGradientLayer frame works
+// override func viewDidLayoutSubviews() {
+// super.viewDidLayoutSubviews()
+// // Update gradient layer frame
+// uvGradientLayer.frame = uvProgressView.bounds
+// }
+//
+// // Function to update the UVIndex
+// func updateUVIndex(_ value: Int) {
+// // Create a mask layer to achieve the progress bar effect
+// let maskLayer = CALayer()
+// let clampedValue = min(max(value, 0), 11)
+// let percentage = CGFloat(clampedValue) / 11.0
+//
+// // The gradient layer should always be full width
+// uvGradientLayer.frame = uvProgressView.bounds
+//
+// // Create a mask that only shows a portion of the gradient
+// maskLayer.frame = CGRect(x: 0, y: 0,
+// width: uvProgressView.bounds.width * percentage,
+// height: uvProgressView.bounds.height)
+// maskLayer.backgroundColor = UIColor.white.cgColor
+//
+// // Apply the mask to the gradient layer
+// uvGradientLayer.mask = maskLayer
+// }
+//}
+
+class iPadUVView: UIView {
+ // MARK: - Properties
+ private let uvStackView: UIStackView = {
+ let stack = UIStackView()
+ stack.translatesAutoresizingMaskIntoConstraints = false
+ stack.layer.cornerRadius = 15
+ stack.axis = .vertical
+ stack.backgroundColor = cyanColor
+ stack.isLayoutMarginsRelativeArrangement = true
+ stack.layoutMargins = UIEdgeInsets(top: 0, left: 10, bottom: 10, right: 10)
+ stack.spacing = 6
+ return stack
+ }()
+
+ private let UVViewTitleLabel: UILabel = {
+ let label = UILabel()
+ label.text = "UV"
+ label.textAlignment = .center
+ label.heightAnchor.constraint(equalToConstant: 30).isActive = true
+ label.font = .boldSystemFont(ofSize: 18.0)
+ return label
+ }()
+
+ private let UVLabel: UILabel = {
+ let label = UILabel()
+ label.text = "--"
+ label.numberOfLines = 2
+ label.textAlignment = .left
+ return label
+ }()
+
+ private let UVStatus: UILabel = {
+ let label = UILabel()
+ label.text = "--"
+ label.textAlignment = .center
+ label.heightAnchor.constraint(equalToConstant: 40).isActive = true
+ label.font = .boldSystemFont(ofSize: 32.0)
+ return label
+ }()
+
+ private let uvProgressView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.layer.cornerRadius = 4
+ view.clipsToBounds = true
+ view.backgroundColor = UIColor.systemGray2.withAlphaComponent(0.3)
+ return view
+ }()
+
+ private let uvGradientLayer: CAGradientLayer = {
+ let layer = CAGradientLayer()
+ layer.colors = [
+ UIColor.systemGreen.cgColor,
+ UIColor.systemYellow.cgColor,
+ UIColor.systemOrange.cgColor,
+ UIColor.systemRed.cgColor,
+ UIColor.systemPurple.cgColor
+ ]
+ layer.startPoint = CGPoint(x: 0, y: 0.5)
+ layer.endPoint = CGPoint(x: 1, y: 0.5)
+ layer.cornerRadius = 2
+ return layer
+ }()
+
+ // MARK: - Init
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupUI()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ setupUI()
+ }
+
+ // MARK: - Setup
+ private func setupUI() {
+ // Add gradient layer to progress view
+ uvProgressView.layer.addSublayer(uvGradientLayer)
+
+ // Add subviews to stack
+ uvStackView.addArrangedSubview(UVViewTitleLabel)
+ uvStackView.addArrangedSubview(UVStatus)
+ uvStackView.addArrangedSubview(uvProgressView)
+ uvStackView.addArrangedSubview(UVLabel)
+
+ // Add stack to view
+ addSubview(uvStackView)
+
+ // Setup constraints
+ NSLayoutConstraint.activate([
+ uvStackView.topAnchor.constraint(equalTo: topAnchor),
+ uvStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ uvStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ uvStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
+
+ uvProgressView.heightAnchor.constraint(equalToConstant: 6)
+ ])
+
+ updateUVIndex(WeatherKitData.UV, WeatherKitData.UVCategory)
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+ uvGradientLayer.frame = uvProgressView.bounds
+ }
+
+ // MARK: - Public Methods
+ func updateUVIndex(_ value1: Int, _ value2: String) {
+ UVLabel.text = "The UV Index is currently \(value2)"
+ print("skibb: \(value2)")
+ UVStatus.text = "\(value1)"
+
+ // Create a mask layer to achieve the progress bar effect
+ let maskLayer = CALayer()
+ let clampedValue = min(max(value1, 0), 11)
+ let percentage = CGFloat(clampedValue) / 11.0
+
+ // The gradient layer should always be full width
+ uvGradientLayer.frame = uvProgressView.bounds
+
+ // Create a mask that only shows a portion of the gradient
+ maskLayer.frame = CGRect(x: 0, y: 0,
+ width: uvProgressView.bounds.width * percentage,
+ height: uvProgressView.bounds.height)
+ maskLayer.backgroundColor = UIColor.white.cgColor
+
+ // Apply the mask to the gradient layer
+ uvGradientLayer.mask = maskLayer
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/WindView.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/WindView.swift
new file mode 100644
index 0000000..25f75dc
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/Components/WindView.swift
@@ -0,0 +1,232 @@
+//
+// WindView.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/14/23.
+//
+
+import Foundation
+import UIKit
+import SwiftUI
+
+
+class WindCompassView: UIView {
+ private let windSpeed: Double
+ private let windDirection: Double // in degrees
+
+ init(frame: CGRect, windSpeed: Double, windDirection: Double) {
+ self.windSpeed = windSpeed
+ self.windDirection = windDirection
+ super.init(frame: frame)
+ backgroundColor = .clear
+ }
+
+ required init?(coder: NSCoder) {
+ self.windSpeed = 0
+ self.windDirection = 0
+ super.init(coder: coder)
+ }
+
+ override func draw(_ rect: CGRect) {
+ guard let context = UIGraphicsGetCurrentContext() else { return }
+
+ let center = CGPoint(x: bounds.midX, y: bounds.midY)
+ let radius = min(bounds.width, bounds.height) * 0.3
+
+ // Draw circle
+ context.setStrokeColor(UIColor.white.withAlphaComponent(0.3).cgColor)
+ context.setLineWidth(1)
+ context.addArc(center: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: false)
+ context.strokePath()
+
+ // Draw tick marks
+ for i in 0..<360 {
+ if i % 30 == 0 {
+ let angle = CGFloat(i) * .pi / 180
+ let startPoint = CGPoint(
+ x: center.x + (radius - 5) * cos(angle),
+ y: center.y + (radius - 5) * sin(angle)
+ )
+ let endPoint = CGPoint(
+ x: center.x + radius * cos(angle),
+ y: center.y + radius * sin(angle)
+ )
+
+ context.move(to: startPoint)
+ context.addLine(to: endPoint)
+ context.strokePath()
+ }
+ }
+
+ let directions = ["N", "E", "S", "W"]
+ let textMargin: CGFloat = 12 // Reduced from 20
+ let positions = [
+ CGPoint(x: center.x, y: center.y - radius - textMargin),
+ CGPoint(x: center.x + radius + textMargin, y: center.y),
+ CGPoint(x: center.x, y: center.y + radius + textMargin),
+ CGPoint(x: center.x - radius - textMargin, y: center.y)
+ ]
+ // Adjust font size based on view size
+ let fontSize = min(bounds.width, bounds.height) * 0.15 // Makes font size relative to view size
+ let attrs: [NSAttributedString.Key: Any] = [
+ .font: UIFont.systemFont(ofSize: fontSize),
+// .font: customFont,
+ .foregroundColor: UIColor.white
+ ]
+
+ // Adjust text positioning to account for font size
+ for (direction, position) in zip(directions, positions) {
+ let textSize = (direction as NSString).size(withAttributes: attrs)
+ let x = position.x - textSize.width / 2
+ let y = position.y - textSize.height / 2
+ (direction as NSString).draw(at: CGPoint(x: x, y: y), withAttributes: attrs)
+ }
+
+ // Adjust center text size
+ let speedText = "\(Int(windSpeed))\nmph"
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.alignment = .center
+
+ let centerFontSize = min(bounds.width, bounds.height) * 0.2 // Makes center text size relative to view size
+ let centerAttrs: [NSAttributedString.Key: Any] = [
+ .font: UIFont.systemFont(ofSize: centerFontSize, weight: .medium),
+ .foregroundColor: UIColor.white,
+ .paragraphStyle: paragraphStyle
+ ]
+
+ let textRect = CGRect(x: center.x - radius/2, y: center.y - radius/4,
+ width: radius, height: radius/2)
+ (speedText as NSString).draw(in: textRect, withAttributes: centerAttrs)
+
+ // Draw arrow
+ context.saveGState()
+ context.translateBy(x: center.x, y: center.y)
+ context.rotate(by: CGFloat(windDirection) * .pi / 180)
+
+ context.setFillColor(UIColor.white.cgColor)
+ context.setStrokeColor(UIColor.white.cgColor)
+ context.setLineWidth(2)
+
+ // Arrow body
+ // context.move(to: CGPoint(x: -radius + 10, y: 0))
+ // context.addLine(to: CGPoint(x: radius - 10, y: 0))
+ context.strokePath()
+
+ // Instead of CGPath(polygonIn:), we'll draw the arrow head manually
+ // Arrow head
+ let arrowHeadLength: CGFloat = 16
+ let arrowHeadWidth: CGFloat = 8
+
+ context.move(to: CGPoint(x: radius - 10, y: 0))
+ context.addLine(to: CGPoint(x: radius - 10 - arrowHeadLength, y: -arrowHeadWidth))
+ context.addLine(to: CGPoint(x: radius - 10 - arrowHeadLength, y: arrowHeadWidth))
+ context.closePath()
+ context.fillPath()
+
+ context.restoreGState()
+ }
+
+ func setupFont() -> UIFont {
+ guard let customFont = UIFont(name: "SpaceX", size: 20.0) else {
+ fatalError("""
+ Failed to load the "SpaceX" font.
+ Make sure the font file is included in the project and the font name is spelled correctly.
+ """
+ )
+ }
+ return customFont
+ }
+}
+
+
+class iPadWindView: UIView {
+ // MARK: - Properties
+ private let windStackView: UIStackView = {
+ let stack = UIStackView()
+ stack.translatesAutoresizingMaskIntoConstraints = false
+ stack.layer.cornerRadius = 15
+ stack.axis = .vertical
+ stack.backgroundColor = cyanColor
+ stack.isLayoutMarginsRelativeArrangement = true
+ stack.layoutMargins = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
+ stack.spacing = 0
+ return stack
+ }()
+
+ private let WindViewTitleLabel: UILabel = {
+ let label = UILabel()
+ label.text = "Wind"
+ label.textAlignment = .center
+ label.font = .boldSystemFont(ofSize: 18.0)
+ return label
+ }()
+
+ private let WindSpeedLabel: UILabel = {
+ let label = UILabel()
+ label.text = "--"
+ label.textAlignment = .center
+ return label
+ }()
+
+ // MARK: - Init
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupUI()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ setupUI()
+ }
+
+ // create compass view
+ private var compassView: WindCompassView!
+
+ // MARK: - Setup
+ private func setupUI() {
+ // Setup compass view
+ compassView = WindCompassView(frame: .zero,
+ windSpeed: Double(WeatherKitData.WindSpeed) ?? 0,
+ windDirection: (WeatherKitData.WindDirectionAngle - 90)) // true north is measured at 0 degrees, graphic starts at 90 though
+ compassView.translatesAutoresizingMaskIntoConstraints = false
+
+ // Add labels to stack
+ windStackView.addArrangedSubview(WindViewTitleLabel)
+
+ // Create a container view for the compass to control its size
+ let compassContainer = UIView()
+ compassContainer.translatesAutoresizingMaskIntoConstraints = false
+ compassContainer.addSubview(compassView)
+
+ // Add compass container to stack
+ windStackView.addArrangedSubview(compassContainer)
+ // windStackView.addArrangedSubview(WindSpeedLabel)
+ // windStackView.addArrangedSubview(compassView)
+
+ // Add stack to view
+ addSubview(windStackView)
+
+ // Setup constraints
+ NSLayoutConstraint.activate([
+ windStackView.topAnchor.constraint(equalTo: topAnchor),
+ windStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ windStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ windStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ // Compass container constraints
+ compassContainer.heightAnchor.constraint(equalToConstant: 150), // Adjust this value
+
+ // Compass view constraints
+ compassView.centerXAnchor.constraint(equalTo: compassContainer.centerXAnchor),
+ compassView.centerYAnchor.constraint(equalTo: compassContainer.centerYAnchor),
+ compassView.widthAnchor.constraint(equalToConstant: 120), // Adjust this value
+ compassView.heightAnchor.constraint(equalToConstant: 120) // Adjust this value
+ ])
+
+ updateWindLabel(WeatherKitData.WindSpeed)
+ }
+
+ // MARK: - Public Methods
+ func updateWindLabel(_ value: String) {
+ WindSpeedLabel.text = "\(value)"
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/iPadDailyCollectionViewCell/iPadDailyCollectionViewCell.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/iPadDailyCollectionViewCell/iPadDailyCollectionViewCell.swift
new file mode 100644
index 0000000..2ab8e47
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/iPadDailyCollectionViewCell/iPadDailyCollectionViewCell.swift
@@ -0,0 +1,115 @@
+//
+// iPadDailyCollectionViewCell.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/19/23.
+//
+
+import UIKit
+
+class iPadDailyCollectionViewCell: UICollectionViewCell {
+ // Add UI elements as needed
+ // topStack is needed so that stackview doesn't slide around
+ var topStack: UIStackView!
+// var stackView: UIStackView!
+ var minMaxStack: UIStackView!
+ var dayOfWeek: UILabel!
+ var weatherIcon: UIImageView!
+ var maxTempLabel: UILabel!
+ var minTempLabel: UILabel!
+ var TTT: UIStackView!
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupUI()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ setupUI()
+ }
+
+ private func setupUI() {
+ // cyanColor for views in mainview
+ let cyanColor = UIColor(red: 95.0/255.0, green: 135.0/255.0, blue: 255.0/255.0, alpha: 0.93)
+
+ topStack = UIStackView()
+ minMaxStack = UIStackView()
+ maxTempLabel = UILabel()
+ minTempLabel = UILabel()
+ dayOfWeek = UILabel()
+ weatherIcon = UIImageView()
+
+ topStack.translatesAutoresizingMaskIntoConstraints = false
+// topStack.backgroundColor = .orange
+ topStack.backgroundColor = cyanColor
+ topStack.applyBlurEffect(cornerRadius: 15)
+// topStack.layer.opacity = 0.5
+// topStack.isOpaque = true
+ topStack.axis = .vertical
+ topStack.layer.cornerRadius = 15
+
+ weatherIcon.translatesAutoresizingMaskIntoConstraints = false
+ weatherIcon.image = UIImage(systemName: "sun.max.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20.0))?.withRenderingMode(.alwaysOriginal)
+ weatherIcon.contentMode = .scaleAspectFit
+
+ minMaxStack.translatesAutoresizingMaskIntoConstraints = false
+ minMaxStack.axis = .horizontal
+ minMaxStack.spacing = 1
+
+ maxTempLabel.translatesAutoresizingMaskIntoConstraints = false
+ maxTempLabel.text = "Max"
+
+ minTempLabel.translatesAutoresizingMaskIntoConstraints = false
+ minTempLabel.text = "Min"
+
+ dayOfWeek = UILabel()
+ dayOfWeek.translatesAutoresizingMaskIntoConstraints = false
+ dayOfWeek.text = "WeekDay"
+ dayOfWeek.textAlignment = .center
+
+ topStack.addArrangedSubview(weatherIcon)
+ topStack.addArrangedSubview(minMaxStack)
+ topStack.addArrangedSubview(dayOfWeek)
+ minMaxStack.addArrangedSubview(minTempLabel)
+ minMaxStack.addArrangedSubview(maxTempLabel)
+ contentView.addSubview(topStack)
+
+ // we set up the font for the labels
+ let customFont: UIFont
+ customFont = setupFont()
+
+ minTempLabel.font = customFont
+ maxTempLabel.font = customFont
+ minTempLabel.adjustsFontSizeToFitWidth = true
+ maxTempLabel.adjustsFontSizeToFitWidth = true
+
+ NSLayoutConstraint.activate([
+// contentView.heightAnchor.constraint(equalToConstant: 200),
+ topStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
+ topStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5),
+ topStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5),
+ topStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
+ // minMaxStack constraints
+// weatherIcon.heightAnchor.constraint(equalToConstant: 60),
+// weatherIcon.widthAnchor.constraint(equalToConstant: 10),
+ minMaxStack.heightAnchor.constraint(equalToConstant: 60),
+ dayOfWeek.heightAnchor.constraint(equalToConstant: 40),
+// minMaxStack.topAnchor.constraint(equalTo: topStack.topAnchor),
+// minMaxStack.bottomAnchor.constraint(equalTo: topStack.bottomAnchor),
+// minMaxStack.leadingAnchor.constraint(equalTo: topStack.leadingAnchor),
+// minMaxStack.trailingAnchor.constraint(equalTo: topStack.trailingAnchor),
+ ])
+ }
+
+ func setupFont() -> UIFont {
+ guard let customFont = UIFont(name: "SpaceX", size: 20.0) else {
+ fatalError("""
+ Failed to load the "SpaceX" font.
+ Make sure the font file is included in the project and the font name is spelled correctly.
+ """
+ )
+ }
+ return customFont
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/iPadMainViewController.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/iPadMainViewController.swift
new file mode 100644
index 0000000..21d1004
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/MainViewController/iPadMainViewController.swift
@@ -0,0 +1,498 @@
+//
+// iPadMainViewController.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 10/31/23.
+//
+
+import Foundation
+import UIKit
+import CoreLocation
+
+let cyanColor = UIColor(red: 95.0/255.0, green: 195.0/255.0, blue: 255.0/255.0, alpha: 0.93)
+
+class iPadMainViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, CLLocationManagerDelegate {
+ //Creates the location manager
+ let locationManager = CLLocationManager()
+
+ // MARK: Top Current Stack Component
+ let customView = iPadMainTopCurrentStack()
+ let mainScrollView = UIScrollView()
+ let humidityView = iPadHumidityStack()
+// let humidityView = UIStackView()
+ let rocketView = UIImageView()
+// let sunsetView = UIStackView()
+ let sunsetView = iPadSunsetView()
+// let UVView = UIStackView()
+ let UVView = iPadUVView()
+// let windView = UIStackView()
+ let windView = iPadWindView()
+// let precipitationView = UIStackView()
+ let precipitationView = iPadPrecipitationView()
+ let pressureView = UIStackView()
+ //Creates view for cloud at top
+ let cloudViewHolder = UIView()
+ //Creates a refresh control for the scrollview
+ var refreshControl = UIRefreshControl()
+
+
+ // collection view for hourly forecast
+ let hourlyForecastView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init()) // need to have frame and layout for UICollectionView
+
+ // collection view for daily forecast
+ let dailyForecastView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
+
+ // cyanColor for views in mainview
+// let cyanColor = UIColor(red: 95.0/255.0, green: 195.0/255.0, blue: 255.0/255.0, alpha: 0.93)
+
+ // our gradient progress bar for uv view
+ let uvProgressView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.layer.cornerRadius = 4
+ view.clipsToBounds = true
+ view.backgroundColor = UIColor.systemGray2.withAlphaComponent(0.3) // Add this line
+ return view
+ }()
+
+ let uvGradientLayer: CAGradientLayer = {
+ let layer = CAGradientLayer()
+ layer.colors = [
+ UIColor.systemGreen.cgColor,
+ UIColor.systemYellow.cgColor,
+ UIColor.systemOrange.cgColor,
+ UIColor.systemRed.cgColor,
+ UIColor.systemPurple.cgColor
+ ]
+ layer.startPoint = CGPoint(x: 0, y: 0.5)
+ layer.endPoint = CGPoint(x: 1, y: 0.5)
+ layer.cornerRadius = 2 // Add this line to match the view's corner radius
+ return layer
+ }()
+
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Ask for Authorisation from the User.
+ self.locationManager.requestAlwaysAuthorization()
+ // For use in foreground
+ self.locationManager.requestWhenInUseAuthorization()
+ // sets SVG background
+ setBackground()
+ setupRocketView()
+ // create humidityView
+// createHumidityView()
+ // create sunsetView
+// createSunsetView()
+ // create UVView
+// createUVView()
+ // create precipitationView
+// createPrecipitationView()
+ // create windView
+// createWindView()
+ // create pressureView
+ createPressureView()
+ // sets up the UI
+ setupUI()
+ // configure the hourly forecast view
+ configureCollectionView()
+ //
+ setupMainScrollView()
+
+ //Sets settings for refreshControl
+ refreshControl.translatesAutoresizingMaskIntoConstraints = false
+ refreshControl.attributedTitle = NSAttributedString("Fetching Weather")
+ refreshControl.addTarget(self, action: #selector(refresh(sender:)), for: UIControl.Event.valueChanged)
+ }
+
+ // sets up the RocketView to the right
+ private func setupRocketView() {
+ //rocketView settings
+ let rocketTapGesture = UITapGestureRecognizer(target: self, action: #selector(launchRocket))
+ rocketTapGesture.isEnabled = true
+ rocketView.translatesAutoresizingMaskIntoConstraints = false
+ rocketView.contentMode = .scaleAspectFit
+ rocketView.clipsToBounds = true
+ rocketView.image = UIImage(named: "Falcon-Heavy.svg")
+ rocketView.layer.shadowColor = UIColor.black.cgColor
+ rocketView.layer.shadowRadius = 15.0
+ rocketView.layer.shadowOpacity = 0.7
+ rocketView.layer.shadowOffset = CGSize(width: -4, height: 4)
+ rocketView.layer.masksToBounds = false
+ rocketView.isUserInteractionEnabled = true
+ rocketView.addGestureRecognizer(rocketTapGesture)
+ }
+
+ private func setupUI() {
+ customView.translatesAutoresizingMaskIntoConstraints = false
+ humidityView.translatesAutoresizingMaskIntoConstraints = false
+ rocketView.translatesAutoresizingMaskIntoConstraints = false
+ hourlyForecastView.translatesAutoresizingMaskIntoConstraints = false
+ dailyForecastView.translatesAutoresizingMaskIntoConstraints = false
+ sunsetView.translatesAutoresizingMaskIntoConstraints = false
+ precipitationView.translatesAutoresizingMaskIntoConstraints = false
+ windView.translatesAutoresizingMaskIntoConstraints = false
+ UVView.translatesAutoresizingMaskIntoConstraints = false
+
+ //Sets settings for refreshControl
+ refreshControl.translatesAutoresizingMaskIntoConstraints = false
+ refreshControl.attributedTitle = NSAttributedString("Fetching Weather")
+ refreshControl.addTarget(self, action: #selector(refresh(sender:)), for: UIControl.Event.valueChanged)
+
+
+ view.backgroundColor = .orange
+
+// mainScrollView.addSubview(customView)
+ mainScrollView.addSubview(humidityView)
+// mainScrollView.addSubview(rocketView)
+ mainScrollView.addSubview(hourlyForecastView)
+ mainScrollView.addSubview(dailyForecastView)
+ mainScrollView.addSubview(sunsetView)
+ mainScrollView.addSubview(UVView)
+ mainScrollView.addSubview(precipitationView)
+ mainScrollView.addSubview(windView)
+ mainScrollView.addSubview(refreshControl)
+
+ view.addSubview(rocketView)
+ view.addSubview(customView)
+ view.addSubview(mainScrollView)
+
+
+ // activates constraints
+ NSLayoutConstraint.activate([
+ //customView constraints
+ customView.topAnchor.constraint(equalTo: view.topAnchor),
+ customView.heightAnchor.constraint(equalToConstant: 100),
+ customView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ customView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ //topStack constraints
+ customView.topStack.topAnchor.constraint(equalTo: customView.topAnchor, constant: 20),
+ customView.topStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
+ customView.topStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 250),
+ //mainScrollView constraints
+ mainScrollView.topAnchor.constraint(equalTo: customView.topStack.bottomAnchor, constant: 20),
+ mainScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 250),
+ mainScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ mainScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ //humidityView constraints
+ humidityView.topAnchor.constraint(equalTo: mainScrollView.topAnchor, constant: 15),
+ humidityView.heightAnchor.constraint(equalToConstant: 100),
+ humidityView.leadingAnchor.constraint(equalTo: mainScrollView.leadingAnchor),
+ humidityView.widthAnchor.constraint(equalToConstant: 180),
+ //rocketView constraints
+ rocketView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
+ rocketView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
+ rocketView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ rocketView.trailingAnchor.constraint(equalTo: mainScrollView.leadingAnchor, constant: 0),
+ // hourly forecast view constraints
+ hourlyForecastView.topAnchor.constraint(equalTo: mainScrollView.topAnchor, constant: 15),
+ hourlyForecastView.leadingAnchor.constraint(equalTo: humidityView.trailingAnchor, constant: 15),
+ hourlyForecastView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ hourlyForecastView.heightAnchor.constraint(equalToConstant: 100),
+ // daily forecast view constraints
+ dailyForecastView.topAnchor.constraint(equalTo: hourlyForecastView.bottomAnchor, constant: 15),
+ dailyForecastView.leadingAnchor.constraint(equalTo: hourlyForecastView.leadingAnchor),
+ dailyForecastView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ dailyForecastView.bottomAnchor.constraint(equalTo: precipitationView.bottomAnchor),
+ // sunsetView constraints
+ sunsetView.topAnchor.constraint(equalTo: humidityView.bottomAnchor, constant: 15),
+ sunsetView.leadingAnchor.constraint(equalTo: humidityView.leadingAnchor),
+ sunsetView.trailingAnchor.constraint(equalTo: humidityView.trailingAnchor),
+ sunsetView.heightAnchor.constraint(equalToConstant: 100),
+ // UVView constraints
+ UVView.topAnchor.constraint(equalTo: dailyForecastView.bottomAnchor, constant: 15),
+ UVView.leadingAnchor.constraint(equalTo: dailyForecastView.leadingAnchor),
+ UVView.widthAnchor.constraint(equalToConstant: 180),
+ UVView.heightAnchor.constraint(equalToConstant: 180),
+ // precipitationView constraints
+ precipitationView.topAnchor.constraint(equalTo: sunsetView.bottomAnchor, constant: 15),
+ precipitationView.leadingAnchor.constraint(equalTo: sunsetView.leadingAnchor),
+ precipitationView.trailingAnchor.constraint(equalTo: sunsetView.trailingAnchor),
+ precipitationView.heightAnchor.constraint(equalToConstant: 100),
+ // windView constraints
+ windView.topAnchor.constraint(equalTo: dailyForecastView.bottomAnchor, constant: 15),
+ windView.leadingAnchor.constraint(equalTo: precipitationView.leadingAnchor),
+ windView.widthAnchor.constraint(equalToConstant: 180),
+ windView.heightAnchor.constraint(equalToConstant: 180),
+ ])
+ }
+
+
+ func setBackground() {
+ //Creates cloud at top of the screen
+ cloudViewHolder.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height / 4)
+ let cloud = UIImage(named: "Cloud.svg")
+ let cloudView : UIImageView!
+ cloudView = UIImageView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height / 4))
+ cloudView.contentMode = .scaleAspectFit
+ cloudView.layer.opacity = 0.6
+ cloudView.clipsToBounds = true
+ cloudView.image = cloud
+ cloudView.center = view.center
+ cloudView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height / 4)
+ cloudViewHolder.addSubview(cloudView)
+ view.sendSubviewToBack(cloudViewHolder)
+
+
+ let background = UIImage(named: "Background.svg")
+ var imageView : UIImageView!
+ imageView = UIImageView(frame: view.bounds)
+ imageView.contentMode = .scaleAspectFill
+ imageView.clipsToBounds = true
+ imageView.image = background
+ imageView.center = view.center
+
+ // add subview here so that layout anchors have view to constrain to
+ view.addSubview(imageView)
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ imageView.topAnchor.constraint(lessThanOrEqualToSystemSpacingBelow: view.topAnchor, multiplier: 1.05),
+ imageView.bottomAnchor.constraint(equalToSystemSpacingBelow: view.bottomAnchor, multiplier: 1.05),
+ imageView.leadingAnchor.constraint(equalToSystemSpacingAfter: view.leadingAnchor, multiplier: -1.05),
+ imageView.trailingAnchor.constraint(equalToSystemSpacingAfter: view.trailingAnchor, multiplier: 1.05)
+ ])
+ view.sendSubviewToBack(imageView)
+
+ // horizontal parallax effects
+ let horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
+ horizontalMotionEffect.minimumRelativeValue = -20
+ horizontalMotionEffect.maximumRelativeValue = 20
+
+ // vertical parallax effects
+ let verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
+ verticalMotionEffect.minimumRelativeValue = -20
+ verticalMotionEffect.maximumRelativeValue = 20
+
+ // adds horizontal and vertical to "motionEffectGroup" which is then added to imageview
+ let motionEffectGroup = UIMotionEffectGroup()
+ motionEffectGroup.motionEffects = [horizontalMotionEffect, verticalMotionEffect]
+ imageView.addMotionEffect(motionEffectGroup)
+ }
+
+
+ //MARK: Hourly Forecast Collection View
+ private func configureCollectionView() {
+ hourlyForecastView.delegate = self
+ hourlyForecastView.dataSource = self
+ dailyForecastView.delegate = self
+ dailyForecastView.dataSource = self
+
+ // stylistic options for hourly view
+ hourlyForecastView.layer.cornerRadius = 15
+ hourlyForecastView.backgroundColor = cyanColor
+ hourlyForecastView.showsHorizontalScrollIndicator = false
+
+ // stylistic options for daily view
+ dailyForecastView.layer.cornerRadius = 15
+ dailyForecastView.backgroundColor = cyanColor
+ dailyForecastView.showsHorizontalScrollIndicator = false
+
+ // Register your custom cell class
+ hourlyForecastView.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
+// Set the collection view's layout to horizontal scroll
+ if let layout2 = hourlyForecastView.collectionViewLayout as? UICollectionViewFlowLayout {
+ layout2.scrollDirection = .horizontal
+ }
+
+ dailyForecastView.register(iPadDailyCollectionViewCell.self, forCellWithReuseIdentifier: "iPadDailyCollectionViewCell")
+ if let layout3 = dailyForecastView.collectionViewLayout as? UICollectionViewFlowLayout {
+ layout3.itemSize = CGSize(width: 150, height: 215)
+ layout3.scrollDirection = .horizontal
+ }
+ }
+
+ // number of hourly cells
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+// if hourlyForecastView, then we have 16 cells, otherwise 10
+ if collectionView == self.hourlyForecastView {
+ return 15
+ }
+ else { return 7 }
+ }
+
+ // populates hourly cells
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+
+ // use hourly cell if hourlyForecastView
+ if collectionView == self.hourlyForecastView {
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
+ cell.tempLabel.text = "--"
+ let currentHour = getCurrentHour(offset: indexPath.row)
+ cell.timeLabel.text = " \(currentHour) " // added some spaces so that cell would be wider
+ cell.timeLabel.font = .systemFont(ofSize: 16.0)
+
+ // checks if there is enough data to show
+ if WeatherKitData.HourlyForecastSymbol.count > 15 {
+ //simple check for when icon is "wind" (doesn't have "fill" option)
+ if WeatherKitData.HourlyForecastSymbol[indexPath.row] != "wind" {
+ cell.weatherIcon.image = UIImage(systemName: "\(WeatherKitData.HourlyForecastSymbol[indexPath.row]).fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 32.0))?.withRenderingMode(.alwaysOriginal)
+ } else {
+ cell.weatherIcon.image = UIImage(systemName: "\(WeatherKitData.HourlyForecastSymbol[indexPath.row])", withConfiguration: UIImage.SymbolConfiguration(pointSize: 32.0))?.withRenderingMode(.alwaysOriginal)
+ }
+ } else { cell.weatherIcon.image = UIImage(systemName: "questionmark")}
+
+ // checks if there is enough data for hourly forecast
+ if WeatherKitData.HourlyForecast.count > 15 {
+ cell.tempLabel.text = "\(Int((round(WeatherKitData.HourlyForecast[indexPath.row])*100)/100))˚"
+ } else { cell.tempLabel.text = "--" }
+
+ return cell
+ }
+
+// else use daily cell (for dailyForecastView)
+ else {
+ let cell2 = collectionView.dequeueReusableCell(withReuseIdentifier: "iPadDailyCollectionViewCell", for: indexPath) as! iPadDailyCollectionViewCell
+ NSLayoutConstraint.activate([
+ cell2.heightAnchor.constraint(equalToConstant: 100)
+ ])
+
+ print("IndexPath: \(indexPath.row)")
+ if WeatherKitData.TempMinForecast.count > 5 {
+ cell2.minTempLabel.text = "\(WeatherKitData.TempMinForecast[indexPath.row])"
+ } else {
+ cell2.minTempLabel.text = "--"
+ }
+
+ if WeatherKitData.TempMaxForecast.count > 5{
+ cell2.maxTempLabel.text = "\(WeatherKitData.TempMaxForecast[indexPath.row])"
+ } else {
+ cell2.maxTempLabel.text = "--"
+ }
+
+ // checks if there is enough data to show
+ if WeatherKitData.forecastSymbol.count > 6 {
+ //simple check for when icon is "wind" (doesn't have "fill" option)
+ if WeatherKitData.forecastSymbol[indexPath.row] != "wind" {
+ cell2.weatherIcon.image = UIImage(systemName: "\(WeatherKitData.forecastSymbol[indexPath.row]).fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 32.0))?.withRenderingMode(.alwaysOriginal)
+ } else {
+ cell2.weatherIcon.image = UIImage(systemName: "\(WeatherKitData.forecastSymbol[indexPath.row])", withConfiguration: UIImage.SymbolConfiguration(pointSize: 32.0))?.withRenderingMode(.alwaysOriginal)
+ }
+ } else { cell2.weatherIcon.image = UIImage(systemName: "questionmark")}
+ print("Cl: \(Calendar.current.weekdaySymbols)")
+
+ var Day = getCurrentDayOfWeek(offset: indexPath.row)
+ Day = Day.localizedCapitalized
+ cell2.dayOfWeek.text = Day
+
+ return cell2
+ }
+ }
+ //MARK: - Function for the pull to refresh on the scrollview
+ @objc func refresh(sender:AnyObject) {
+ // Code to refresh table view
+ print("userLatitude: \((UserLocation.userLatitude)!)")
+ print("userLongitude: \((UserLocation.userLongitude)!)")
+ DispatchQueue.main.async {
+ self.refreshControl.endRefreshing()
+ self.fetchFromReload()
+ }
+ }
+
+ //Creates a function for running fetchWeather from reload func
+ func fetchFromReload() {
+ DispatchQueue.main.async {
+ if UserLocation.userCLLocation != nil {
+ WeatherKitData.HourlyForecast.removeAll()
+ WeatherKitData.TempMaxForecast.removeAll()
+ WeatherKitData.TempMinForecast.removeAll()
+ self.getWeather(location: UserLocation.userCLLocation!)
+ } else {
+ }
+ self.updateLabels()
+ self.hourlyForecastView.reloadData()
+ self.dailyForecastView.reloadData()
+ }
+ }
+
+ //MARK: - Function to refresh all of the labels
+ func updateLabels() {
+ //Checks if weatherkit returned symbol is "wind"
+ //This is because "wind" (SF Symbol) has no fill option
+ /*
+ if WeatherKitData.Symbol != "wind" {
+ self.iconView.image = UIImage(systemName: WeatherKitData.Symbol + ".fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 64.0))?.withRenderingMode(.alwaysOriginal)
+ } else {
+ self.iconView.image = UIImage(systemName: WeatherKitData.Symbol, withConfiguration: UIImage.SymbolConfiguration(pointSize: 64.0))?.withRenderingMode(.alwaysOriginal)
+ }
+ */
+ self.customView.currentTempLabel.text = "Current: \(WeatherKitData.Temp)"
+ self.customView.maxTempLabel.text = "High: \(WeatherKitData.TempMax)"
+ self.customView.minTempLabel.text = "Low: \(WeatherKitData.TempMin)"
+ self.humidityView.updateHumidityLabels(WeatherKitData.Humidity)
+ self.precipitationView.updatePrecipitationLabel(WeatherKitData.PrecipitationChance)
+ self.sunsetView.updateSunsetLabels(WeatherKitData.Sunrise, WeatherKitData.Sunset)
+ self.windView.updateWindLabel(WeatherKitData.WindSpeed)
+ self.UVView.updateUVIndex(WeatherKitData.UV, WeatherKitData.UVCategory)
+// self.windLabel.text = "\(WeatherKitData.WindSpeed)"
+// self.precipitationLabel.text = "\(WeatherKitData.PrecipitationChance)% Chance"
+ DateConverter().timeArrayMaker()
+ }
+
+ func getCurrentDayOfWeek(offset: Int) -> String {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "EEEE" // "EEEE" will give you the full day name like "Monday", "Tuesday", etc.
+
+ var currentDate = Date()
+
+ let daysToAdd = offset
+ if let newDate = Calendar.current.date(byAdding: .day, value: daysToAdd, to: currentDate) {
+ currentDate = newDate
+ }
+
+ let dayOfWeekString = dateFormatter.string(from: currentDate)
+
+ return dayOfWeekString
+ }
+ func getCurrentHour(offset: Int) -> String {
+ let dateFormatter = DateFormatter()
+ dateFormatter.dateFormat = "h a" // "EEEE" will give you the full day name like "Monday", "Tuesday", etc.
+
+ var currentHour = Date()
+
+ let hoursToAdd = offset
+ if let newHour = Calendar.current.date(byAdding: .hour, value: hoursToAdd, to: currentHour) {
+ currentHour = newHour
+ }
+
+ let currentHourString = dateFormatter.string(from: currentHour)
+
+ return currentHourString
+ }
+
+ //MARK: - Function to refresh all of the labels
+// func updateLabels() {
+// self.customView.currentTempLabel.text = "\(WeatherKitData.Temp)"
+// self.customView.minTempLabel.text = "Low Temp:\(WeatherKitData.TempMin)"
+// self.customView.maxTempLabel.text = "High Temp:\(WeatherKitData.TempMax)"
+// }
+
+ //Function for checking location manager status (starts getting the location if allowed)
+ func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
+ let status = manager.authorizationStatus
+
+ switch status {
+ case .notDetermined:
+ print("status: notDetermined")
+ case .denied:
+ customView.currentCityLabel.text = "No Location!"
+ print("status: denied")
+ case .restricted:
+ customView.currentCityLabel.text = "No Location!"
+ print("status: restricted")
+ case .authorizedAlways:
+ print("status: authorizedAlways")
+ locationManager.startUpdatingLocation()
+// viewDidLoadRefresh()
+ if UserLocation.userCLLocation != nil {
+ getWeather(location: UserLocation.userCLLocation!)
+ }
+ case .authorizedWhenInUse:
+ print("status: authorizedWhenInUse")
+ locationManager.startUpdatingLocation()
+
+// viewDidLoadRefresh()
+ default:
+ print("unknown ")
+ }
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPad View Controllers/Utils/iPadWeatherKitCallUtil.swift b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/Utils/iPadWeatherKitCallUtil.swift
new file mode 100644
index 0000000..98bdfee
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPad View Controllers/Utils/iPadWeatherKitCallUtil.swift
@@ -0,0 +1,201 @@
+//
+// iPadWeatherKitCallUtil.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/20/23.
+//
+
+import Foundation
+import WeatherKit
+import CoreLocation
+import UIKit
+
+
+//let weatherService = WeatherService()
+
+extension iPadMainViewController {
+ func getWeather(location: CLLocation) {
+ Task{
+ do {
+ //fetches location
+ UserLocation.userCLLocation?.fetchCityAndCountry(completion: { city, country, error in
+ guard let city = city, let country = country, error == nil else { return }
+ print(city + ", " + country) // City, Country
+ //Puts city name into cityLabel
+// self.cityLabel.text = "\(city)"
+ self.customView.currentCityLabel.text = "\(city)"
+// self.cityLabel.text = "San Francisco"
+ })
+// let TempLocation = CLLocation(latitude: 39.9042, longitude: 116.4074) // Beijing
+
+ let calendar = Calendar.current
+ let endDate = calendar.date(byAdding: .hour, value: 12,to: Date.now)
+ let result = try await weatherService.weather(for: location, including: .current, .hourly(startDate: Date.now, endDate: endDate!), .daily)
+
+ let dateFormatter = DateFormatter()
+ dateFormatter.timeStyle = DateFormatter.Style.short
+ dateFormatter.dateStyle = DateFormatter.Style.none
+ dateFormatter.timeZone = .current
+
+ // will have 0 maximumFractionDigits
+ let MF0 = MeasurementFormatter()
+ MF0.unitStyle = .short
+ MF0.locale = .autoupdatingCurrent
+ MF0.numberFormatter.maximumFractionDigits = 0
+
+ // will have 1 maximumFractionDigits
+ let MF1 = MeasurementFormatter()
+ MF1.unitStyle = .short
+ MF1.locale = .autoupdatingCurrent
+ MF1.numberFormatter.maximumFractionDigits = 1
+
+ //Data from currentWeather
+ let temp = MF0.string(from: result.0.temperature)
+
+ MF0.numberFormatter.maximumFractionDigits = 3
+ let uv = result.0.uvIndex.value
+ let uvCategory = result.0.uvIndex.category.description
+ let windSpeed = MF0.string(from: result.0.wind.speed)
+ print(windSpeed)
+ let windDirection = result.0.wind.compassDirection
+ let windDirectionAngle = result.0.wind.direction.converted(to: .degrees)
+ print("gust: \(result.0.wind.gust)")
+ let symbol = result.0.symbolName
+ let humidity = Int(100 * result.0.humidity)
+ let pressure = MF1.string(from: result.0.pressure)
+
+ //Data from dailyForecast[0] (today)
+ let tempMax = MF0.string(from: result.2[0].highTemperature)
+ let tempMin = MF0.string(from: result.2[0].lowTemperature)
+ let localSunrise = dateFormatter.string(from: result.2.forecast[0].sun.sunrise!)
+ let localSunset = dateFormatter.string(from: result.2.forecast[0].sun.sunset!)
+ let solarNoon = dateFormatter.string(from: result.2.forecast[0].sun.solarNoon!)
+ let astronomicalDawn = dateFormatter.string(from: result.2[0].sun.astronomicalDawn!)
+ let astronomicalDusk = dateFormatter.string(from: result.2[0].sun.astronomicalDusk!)
+
+ var rainChance = Int(100 * result.2.forecast[0].precipitationChance)
+ if rainChance < 15 {
+ rainChance = 0
+ } else {}
+
+ //For loop for the tempMax for 5 days
+ for i in 0...6 {
+// print(result.dailyForecast[i].highTemperature)
+ let maxTemp = result.2[i].highTemperature
+ WeatherKitData.TempMaxForecast.append(MF0.string(from: maxTemp)) //Append is needed to append into array
+ print("TempMaxForecast: \(WeatherKitData.TempMaxForecast[i])")
+ let forecastSymbol = result.2[i].symbolName
+ WeatherKitData.forecastSymbol.append(forecastSymbol)
+ print("forecastSymbol: \(WeatherKitData.forecastSymbol[i])")
+ let minTemp = result.2[i].lowTemperature
+ WeatherKitData.TempMinForecast.append(MF0.string(from: minTemp))
+ print("TempMinForecast: \(WeatherKitData.TempMinForecast[i])")
+// print("WEATHERKITDATA TempMax array: \(WeatherKitData.TempMaxForecast[i])")
+ }
+
+ //For loop for 12hours
+ for i in 0...11 {
+ // wind speed for next 12 hours
+ let formatter = MeasurementFormatter()
+ formatter.unitOptions = .temperatureWithoutUnit
+ let windSpeed = result.1.forecast[i].wind.speed
+ let wind = (round(windSpeed.value * 10)) / 10
+// print("Wind: \(wind)")
+ WeatherKitData.WindSpeedForecast.append(wind)
+
+ // gusts for next 12 hours`
+ let gust = result.1.forecast[i].wind.gust
+ if let unwrapperGust = gust {
+ WeatherKitData.WindGusts.append(unwrapperGust)
+ print("gust#\(i): \(unwrapperGust)")
+ }
+
+ // precipitation chance for next 12 hours
+ print("precipitation chance\(i): \(result.1.forecast[i].precipitationChance)")
+ // doesn't need to be unwrapped because "...forecast[i].precipitationChance" is not optional
+ let precipitationChance = result.1.forecast[i].precipitationChance
+ WeatherKitData.PrecipitationChanceForecast.append(precipitationChance * 100) // *100 is needed because precipitationChance is from 0-1
+
+
+ }
+ print("PrecipitatiomnChanceForecast: \(WeatherKitData.PrecipitationChanceForecast)")
+
+ //For loop for 12 hour weather
+ for i in 0...11 {
+ let forecast = result.1.forecast[i].temperature.value
+ WeatherKitData.HourlyForecast.append(forecast)
+ print("Hourly Forecast: \(WeatherKitData.HourlyForecast[i])")
+ let symbol = result.1.forecast[i].symbolName
+ WeatherKitData.HourlyForecastSymbol.append(symbol)
+ }
+
+// print(temp)
+// print(uv)
+// print(symbol)
+// print(rainChance)
+// print(result.1.forecast[0].wind)
+// if result.alerts!.count > 0 {
+// print("Weather Alert: \(result.weatherAlerts?[0].summary)")
+// } else {
+// print("No alerts")
+// }
+
+ print(Double(WeatherKitData.WindSpeedForecast[1]))
+ //Puts fetched data into WeatherKitData struct
+ WeatherKitData.Temp = temp
+ WeatherKitData.TempMax = tempMax
+ WeatherKitData.TempMin = tempMin
+ WeatherKitData.UV = uv
+ WeatherKitData.UVCategory = uvCategory
+ WeatherKitData.WindSpeed = windSpeed
+ WeatherKitData.WindDirection = "\(windDirection)"
+ WeatherKitData.WindDirectionAngle = windDirectionAngle.value
+ WeatherKitData.Symbol = symbol
+ WeatherKitData.Humidity = humidity
+ WeatherKitData.Symbol = symbol
+ WeatherKitData.Sunrise = result.2.forecast[0].sun.sunrise!
+ WeatherKitData.Sunset = result.2.forecast[0].sun.sunset!
+ WeatherKitData.localSunrise = localSunrise
+ WeatherKitData.localSunset = localSunset
+ WeatherKitData.SolarNoon = solarNoon
+ WeatherKitData.AstronomicalDawn = astronomicalDawn
+ WeatherKitData.AstronomicalDusk = astronomicalDusk
+ WeatherKitData.Pressure = pressure
+ WeatherKitData.PrecipitationChance = rainChance
+
+
+ WeatherKitData.SunriseDate = result.2.forecast[0].sun.sunrise!
+ WeatherKitData.SunsetDate = result.2.forecast[0].sun.sunset!
+
+
+ DateConverter().timeArrayMaker() //Runs timeArrayMaker func for timeArray in widget
+ //puts WidgetData struct into widget
+ var widget = WidgetData(temp: temp, tempMax: tempMax, tempMin: tempMin, symbolName: symbol, hourlyForecast: WeatherKitData.HourlyForecast, forecastTimeArray: timeArray.formattedHours)
+ let primaryData = PrimaryData(widgetData: widget)
+ //Encodes data into AppGroup
+ primaryData.encode()
+
+ } catch {
+ print(String(describing: error))
+ }
+ self.updateLabelsAfterAwait()
+ }
+ }
+ //@MainActor runs after getweather Task completes
+ //@MainActor ensures all code runs on main dispatch thread
+ @MainActor
+ private func updateLabelsAfterAwait() {
+ ForecastListVC().forecastTableView.reloadData()
+ print("updateLabelsAfterAwait run")
+// getWeatherLabelUpdate()
+ DateConverter().convertDateToEpoch()
+ createNotification()
+
+ let date = WeatherKitData.SunsetDate
+ let interval = WeatherKitData.SunsetDate.timeIntervalSinceReferenceDate - WeatherKitData.SunriseDate.timeIntervalSinceReferenceDate
+ print("interval time: \(interval)")
+
+// let rocketTimer = Timer(fireAt: date, interval: interval, target: self, selector: #selector(AnimateRocket), userInfo: nil, repeats: false)
+// RunLoop.main.add(rocketTimer, forMode: RunLoop.Mode.common)
+ }
+}
diff --git a/programmatic-ui-weather-app/iPad Version/iPadMainTopCurrentStack/iPadMainTopCurrentStack.swift b/programmatic-ui-weather-app/iPad Version/iPadMainTopCurrentStack/iPadMainTopCurrentStack.swift
new file mode 100644
index 0000000..11bacdc
--- /dev/null
+++ b/programmatic-ui-weather-app/iPad Version/iPadMainTopCurrentStack/iPadMainTopCurrentStack.swift
@@ -0,0 +1,113 @@
+//
+// iPadMainTopCurrentStack.swift
+// weatherkit-weather-app
+//
+// Created by Ruslan Spirkin on 11/1/23.
+//
+
+import UIKit
+
+class iPadMainTopCurrentStack: UIView {
+
+ // MARK: START: stuff needed for UIView
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupUI()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ }
+ // MARK: END
+
+
+ // Topmost stack
+ let topStack: UIStackView = {
+ let stack = UIStackView()
+ stack.axis = .vertical
+ stack.spacing = 15
+ return stack
+ }()
+
+ // Current City Label
+ let currentCityLabel: UILabel = {
+ let label = UILabel()
+ label.text = "No Location!"
+ label.textAlignment = .center
+ label.translatesAutoresizingMaskIntoConstraints = false
+ return label
+ }()
+
+ // Top Info Stack
+ let topInfoStack: UIStackView = {
+ let stack = UIStackView()
+ stack.axis = .horizontal
+ stack.spacing = 15
+ stack.distribution = .equalCentering
+ return stack
+ }()
+
+ // Min Temperature Label
+ let minTempLabel: UILabel = {
+ let label = UILabel()
+ label.text = "--"
+ label.textAlignment = .center
+ label.translatesAutoresizingMaskIntoConstraints = false
+ return label
+ }()
+
+ // Current Temp Label
+ let currentTempLabel: UILabel = {
+ let label = UILabel()
+ label.text = "--"
+ label.textAlignment = .center
+ label.translatesAutoresizingMaskIntoConstraints = false
+ return label
+ }()
+
+ // Max Temperature Label
+ let maxTempLabel: UILabel = {
+ let label = UILabel()
+ label.text = "--"
+ label.translatesAutoresizingMaskIntoConstraints = false
+ return label
+ }()
+
+
+ private func setupUI() {
+ var customFont: UIFont
+ customFont = setupFont()
+
+ topInfoStack.addArrangedSubview(minTempLabel)
+ topInfoStack.addArrangedSubview(currentTempLabel)
+ topInfoStack.addArrangedSubview(maxTempLabel)
+
+ topStack.addArrangedSubview(currentCityLabel)
+ topStack.addArrangedSubview(topInfoStack)
+
+ addSubview(topStack)
+
+ // Really important
+// minTempLabel.translatesAutoresizingMaskIntoConstraints = false
+// currentTempLabel.translatesAutoresizingMaskIntoConstraints = false
+// maxTempLabel.translatesAutoresizingMaskIntoConstraints = false
+// currentCityLabel.translatesAutoresizingMaskIntoConstraints = false
+ topInfoStack.translatesAutoresizingMaskIntoConstraints = false
+ topStack.translatesAutoresizingMaskIntoConstraints = false
+
+ // Set font for the labels
+ currentCityLabel.font = customFont
+ }
+
+ func setupFont() -> UIFont {
+ guard let customFont = UIFont(name: "SpaceX", size: 24.0) else {
+ fatalError("""
+ Failed to load the "SpaceX" font.
+ Make sure the font file is included in the project and the font name is spelled correctly.
+ """
+ )
+ }
+ return customFont
+ }
+
+}
diff --git a/weatherkit-weather-app.xcodeproj/project.pbxproj b/weatherkit-weather-app.xcodeproj/project.pbxproj
index 4f045b9..fafc520 100644
--- a/weatherkit-weather-app.xcodeproj/project.pbxproj
+++ b/weatherkit-weather-app.xcodeproj/project.pbxproj
@@ -8,9 +8,13 @@
/* Begin PBXBuildFile section */
C006DE2D2963CB5B00E8A44B /* InfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C006DE2C2963CB5B00E8A44B /* InfoVC.swift */; };
+ C0160AB22B2A62A70016F9B0 /* SunsetSunriseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0160AB12B2A62A70016F9B0 /* SunsetSunriseView.swift */; };
+ C0160AB32B2A62A70016F9B0 /* SunsetSunriseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0160AB12B2A62A70016F9B0 /* SunsetSunriseView.swift */; };
+ C01634662D12422F00E63C45 /* MainScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01634652D12422F00E63C45 /* MainScrollView.swift */; };
+ C01634672D12422F00E63C45 /* MainScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01634652D12422F00E63C45 /* MainScrollView.swift */; };
C034703F28F6221900656AA8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034703E28F6221900656AA8 /* AppDelegate.swift */; };
C034704128F6221900656AA8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034704028F6221900656AA8 /* SceneDelegate.swift */; };
- C034704328F6221900656AA8 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034704228F6221900656AA8 /* MainViewController.swift */; };
+ C034704328F6221900656AA8 /* iPhoneMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034704228F6221900656AA8 /* iPhoneMainViewController.swift */; };
C034704828F6221A00656AA8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C034704728F6221A00656AA8 /* Assets.xcassets */; };
C034704B28F6221A00656AA8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C034704928F6221A00656AA8 /* LaunchScreen.storyboard */; };
C034706028FB08AF00656AA8 /* DateConverterUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034705F28FB08AF00656AA8 /* DateConverterUtil.swift */; };
@@ -57,6 +61,26 @@
C0EDB92D2A2429D300C5C1BF /* SpaceX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C0EDB92C2A2429D300C5C1BF /* SpaceX.ttf */; };
C0EDB92E2A2429D300C5C1BF /* SpaceX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C0EDB92C2A2429D300C5C1BF /* SpaceX.ttf */; };
C0F341142ABD2AA9006501A7 /* AppDelegateBackgrounRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0F341132ABD2AA9006501A7 /* AppDelegateBackgrounRefresh.swift */; };
+ C0FDAA522AF21E8A00BAB570 /* iPadMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA512AF21E8A00BAB570 /* iPadMainViewController.swift */; };
+ C0FDAA532AF21E8A00BAB570 /* iPadMainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA512AF21E8A00BAB570 /* iPadMainViewController.swift */; };
+ C0FDAA562AF2C84E00BAB570 /* iPadMainTopCurrentStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA552AF2C84E00BAB570 /* iPadMainTopCurrentStack.swift */; };
+ C0FDAA572AF2C84E00BAB570 /* iPadMainTopCurrentStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA552AF2C84E00BAB570 /* iPadMainTopCurrentStack.swift */; };
+ C0FDAA5E2AF761FF00BAB570 /* LaunchRocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA5D2AF761FF00BAB570 /* LaunchRocket.swift */; };
+ C0FDAA5F2AF761FF00BAB570 /* LaunchRocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA5D2AF761FF00BAB570 /* LaunchRocket.swift */; };
+ C0FDAA622AF832C100BAB570 /* HumidityStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA612AF832C100BAB570 /* HumidityStackView.swift */; };
+ C0FDAA632AF832C100BAB570 /* HumidityStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA612AF832C100BAB570 /* HumidityStackView.swift */; };
+ C0FDAA6A2B0354FF00BAB570 /* UVView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA692B0354FF00BAB570 /* UVView.swift */; };
+ C0FDAA6E2B045CC900BAB570 /* PrecipitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA6D2B045CC900BAB570 /* PrecipitationView.swift */; };
+ C0FDAA722B0478B400BAB570 /* WindView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA712B0478B400BAB570 /* WindView.swift */; };
+ C0FDAA742B0478BD00BAB570 /* PrecipitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA6D2B045CC900BAB570 /* PrecipitationView.swift */; };
+ C0FDAA752B0478BF00BAB570 /* UVView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA692B0354FF00BAB570 /* UVView.swift */; };
+ C0FDAA762B047CDB00BAB570 /* WindView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA712B0478B400BAB570 /* WindView.swift */; };
+ C0FDAA782B04808200BAB570 /* PressureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA772B04808200BAB570 /* PressureView.swift */; };
+ C0FDAA792B04808200BAB570 /* PressureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA772B04808200BAB570 /* PressureView.swift */; };
+ C0FDAA7C2B0B311D00BAB570 /* iPadDailyCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA7B2B0B311D00BAB570 /* iPadDailyCollectionViewCell.swift */; };
+ C0FDAA7D2B0B311D00BAB570 /* iPadDailyCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA7B2B0B311D00BAB570 /* iPadDailyCollectionViewCell.swift */; };
+ C0FDAA802B0C340800BAB570 /* iPadWeatherKitCallUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA7F2B0C340800BAB570 /* iPadWeatherKitCallUtil.swift */; };
+ C0FDAA812B0C340800BAB570 /* iPadWeatherKitCallUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FDAA7F2B0C340800BAB570 /* iPadWeatherKitCallUtil.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -92,15 +116,17 @@
/* Begin PBXFileReference section */
C006DE2C2963CB5B00E8A44B /* InfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoVC.swift; sourceTree = ""; };
+ C0160AB12B2A62A70016F9B0 /* SunsetSunriseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SunsetSunriseView.swift; sourceTree = ""; };
C01617CC298C6016006E7C0C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
C01617CE298C6018006E7C0C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
C01617D1298C6018006E7C0C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
C01617DA298C6407006E7C0C /* weatherkit_watchosExtensionApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = weatherkit_watchosExtensionApp.swift; sourceTree = ""; };
+ C01634652D12422F00E63C45 /* MainScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScrollView.swift; sourceTree = ""; };
C01B200D2995EC21000363CA /* CurrentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentView.swift; sourceTree = ""; };
C034703B28F6221900656AA8 /* weatherkit-weather-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "weatherkit-weather-app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
C034703E28F6221900656AA8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
C034704028F6221900656AA8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
- C034704228F6221900656AA8 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; };
+ C034704228F6221900656AA8 /* iPhoneMainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPhoneMainViewController.swift; sourceTree = ""; };
C034704728F6221A00656AA8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
C034704A28F6221A00656AA8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
C034704C28F6221A00656AA8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
@@ -150,6 +176,16 @@
C0EDB9292A22BDF600C5C1BF /* WindSpeedPopUpVCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindSpeedPopUpVCTests.swift; sourceTree = ""; };
C0EDB92C2A2429D300C5C1BF /* SpaceX.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = SpaceX.ttf; sourceTree = ""; };
C0F341132ABD2AA9006501A7 /* AppDelegateBackgrounRefresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateBackgrounRefresh.swift; sourceTree = ""; };
+ C0FDAA512AF21E8A00BAB570 /* iPadMainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPadMainViewController.swift; sourceTree = ""; };
+ C0FDAA552AF2C84E00BAB570 /* iPadMainTopCurrentStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPadMainTopCurrentStack.swift; sourceTree = ""; };
+ C0FDAA5D2AF761FF00BAB570 /* LaunchRocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchRocket.swift; sourceTree = ""; };
+ C0FDAA612AF832C100BAB570 /* HumidityStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HumidityStackView.swift; sourceTree = ""; };
+ C0FDAA692B0354FF00BAB570 /* UVView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UVView.swift; sourceTree = ""; };
+ C0FDAA6D2B045CC900BAB570 /* PrecipitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrecipitationView.swift; sourceTree = ""; };
+ C0FDAA712B0478B400BAB570 /* WindView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindView.swift; sourceTree = ""; };
+ C0FDAA772B04808200BAB570 /* PressureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PressureView.swift; sourceTree = ""; };
+ C0FDAA7B2B0B311D00BAB570 /* iPadDailyCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPadDailyCollectionViewCell.swift; sourceTree = ""; };
+ C0FDAA7F2B0C340800BAB570 /* iPadWeatherKitCallUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPadWeatherKitCallUtil.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -247,6 +283,7 @@
C034703D28F6221900656AA8 /* programmatic-ui-weather-app */ = {
isa = PBXGroup;
children = (
+ C0FDAA502AF21E0B00BAB570 /* iPad Version */,
C0B01BCA2912B08A00D887C6 /* programmatic-ui-weather-app.entitlements */,
C034705328F640AD00656AA8 /* Delegates */,
C0A211182952E20B00C05917 /* AnimationsEffects */,
@@ -373,7 +410,7 @@
isa = PBXGroup;
children = (
C072FF442A742F6700CCF9B1 /* HourlyCollectionViewCell */,
- C034704228F6221900656AA8 /* MainViewController.swift */,
+ C034704228F6221900656AA8 /* iPhoneMainViewController.swift */,
);
path = MainVC;
sourceTree = "";
@@ -452,6 +489,81 @@
path = Fonts;
sourceTree = "";
};
+ C0FDAA502AF21E0B00BAB570 /* iPad Version */ = {
+ isa = PBXGroup;
+ children = (
+ C0FDAA652AFB48DE00BAB570 /* iPad View Controllers */,
+ C0FDAA5C2AF761E100BAB570 /* Rocket Utils */,
+ C0FDAA542AF2C82600BAB570 /* iPadMainTopCurrentStack */,
+ );
+ path = "iPad Version";
+ sourceTree = "";
+ };
+ C0FDAA542AF2C82600BAB570 /* iPadMainTopCurrentStack */ = {
+ isa = PBXGroup;
+ children = (
+ C0FDAA552AF2C84E00BAB570 /* iPadMainTopCurrentStack.swift */,
+ );
+ path = iPadMainTopCurrentStack;
+ sourceTree = "";
+ };
+ C0FDAA5C2AF761E100BAB570 /* Rocket Utils */ = {
+ isa = PBXGroup;
+ children = (
+ C0FDAA5D2AF761FF00BAB570 /* LaunchRocket.swift */,
+ );
+ path = "Rocket Utils";
+ sourceTree = "";
+ };
+ C0FDAA652AFB48DE00BAB570 /* iPad View Controllers */ = {
+ isa = PBXGroup;
+ children = (
+ C0FDAA7E2B0C33F000BAB570 /* Utils */,
+ C0FDAA6B2B03560500BAB570 /* MainViewController */,
+ );
+ path = "iPad View Controllers";
+ sourceTree = "";
+ };
+ C0FDAA6B2B03560500BAB570 /* MainViewController */ = {
+ isa = PBXGroup;
+ children = (
+ C0FDAA7A2B0B30F900BAB570 /* iPadDailyCollectionViewCell */,
+ C0FDAA6C2B03561300BAB570 /* Components */,
+ C0FDAA512AF21E8A00BAB570 /* iPadMainViewController.swift */,
+ );
+ path = MainViewController;
+ sourceTree = "";
+ };
+ C0FDAA6C2B03561300BAB570 /* Components */ = {
+ isa = PBXGroup;
+ children = (
+ C0FDAA692B0354FF00BAB570 /* UVView.swift */,
+ C0FDAA6D2B045CC900BAB570 /* PrecipitationView.swift */,
+ C0FDAA712B0478B400BAB570 /* WindView.swift */,
+ C0FDAA772B04808200BAB570 /* PressureView.swift */,
+ C0160AB12B2A62A70016F9B0 /* SunsetSunriseView.swift */,
+ C0FDAA612AF832C100BAB570 /* HumidityStackView.swift */,
+ C01634652D12422F00E63C45 /* MainScrollView.swift */,
+ );
+ path = Components;
+ sourceTree = "";
+ };
+ C0FDAA7A2B0B30F900BAB570 /* iPadDailyCollectionViewCell */ = {
+ isa = PBXGroup;
+ children = (
+ C0FDAA7B2B0B311D00BAB570 /* iPadDailyCollectionViewCell.swift */,
+ );
+ path = iPadDailyCollectionViewCell;
+ sourceTree = "";
+ };
+ C0FDAA7E2B0C33F000BAB570 /* Utils */ = {
+ isa = PBXGroup;
+ children = (
+ C0FDAA7F2B0C340800BAB570 /* iPadWeatherKitCallUtil.swift */,
+ );
+ path = Utils;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -605,26 +717,38 @@
C0CF2DD02929487800359476 /* SettingsCell.swift in Sources */,
C0A2111A2952E21C00C05917 /* AnimationsEffects.swift in Sources */,
C06F8D31292981680054D5DC /* Forecasts.swift in Sources */,
+ C0FDAA5E2AF761FF00BAB570 /* LaunchRocket.swift in Sources */,
C006DE2D2963CB5B00E8A44B /* InfoVC.swift in Sources */,
C0F341142ABD2AA9006501A7 /* AppDelegateBackgrounRefresh.swift in Sources */,
+ C0FDAA6A2B0354FF00BAB570 /* UVView.swift in Sources */,
C034706028FB08AF00656AA8 /* DateConverterUtil.swift in Sources */,
C0711C2C2949667B004D3BD3 /* WindSpeedPopUpVC.swift in Sources */,
C0B01BF3291F361900D887C6 /* TabBarViewController.swift in Sources */,
C0711C2929484FD3004D3BD3 /* SunriseSunsetPopUpVC.swift in Sources */,
C0D265C0297635480079FBE3 /* WeatherKitData.swift in Sources */,
C04E9E9F29417FA100882FC6 /* WidgetStruct.swift in Sources */,
+ C0FDAA562AF2C84E00BAB570 /* iPadMainTopCurrentStack.swift in Sources */,
C072FF462A742F8900CCF9B1 /* HourlyCollectionViewCell.swift in Sources */,
C0CF2DD22929487800359476 /* SettingsListVC.swift in Sources */,
- C034704328F6221900656AA8 /* MainViewController.swift in Sources */,
+ C034704328F6221900656AA8 /* iPhoneMainViewController.swift in Sources */,
+ C0FDAA622AF832C100BAB570 /* HumidityStackView.swift in Sources */,
C05606FE2A61CEBA00F3C705 /* SwiftUIBackgroundBlur.swift in Sources */,
C0CF2DD12929487800359476 /* Settings.swift in Sources */,
+ C0FDAA782B04808200BAB570 /* PressureView.swift in Sources */,
C04E9EEE2945296C00882FC6 /* PrimaryData.swift in Sources */,
+ C0FDAA722B0478B400BAB570 /* WindView.swift in Sources */,
C06F8D2E292980550054D5DC /* ForecastListVC.swift in Sources */,
+ C0160AB22B2A62A70016F9B0 /* SunsetSunriseView.swift in Sources */,
+ C0FDAA802B0C340800BAB570 /* iPadWeatherKitCallUtil.swift in Sources */,
C055633C295CE4CB008CB6D4 /* NotificationUtil.swift in Sources */,
C05606FC2A612DA800F3C705 /* PrecipitationPopUpVC.swift in Sources */,
+ C01634662D12422F00E63C45 /* MainScrollView.swift in Sources */,
C0B01BCC291334AC00D887C6 /* WeatherKitCallUtil.swift in Sources */,
C034703F28F6221900656AA8 /* AppDelegate.swift in Sources */,
+ C0FDAA522AF21E8A00BAB570 /* iPadMainViewController.swift in Sources */,
+ C0FDAA7C2B0B311D00BAB570 /* iPadDailyCollectionViewCell.swift in Sources */,
C034704128F6221900656AA8 /* SceneDelegate.swift in Sources */,
+ C0FDAA6E2B045CC900BAB570 /* PrecipitationView.swift in Sources */,
C053221E295D52F100FF849E /* RocketAnimationUtil.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -643,8 +767,20 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ C0FDAA742B0478BD00BAB570 /* PrecipitationView.swift in Sources */,
+ C0FDAA7D2B0B311D00BAB570 /* iPadDailyCollectionViewCell.swift in Sources */,
C0EDB9232A22BDD400C5C1BF /* weatherkit_weather_apptests.swift in Sources */,
+ C0FDAA5F2AF761FF00BAB570 /* LaunchRocket.swift in Sources */,
+ C01634672D12422F00E63C45 /* MainScrollView.swift in Sources */,
+ C0FDAA762B047CDB00BAB570 /* WindView.swift in Sources */,
C0EDB92A2A22BDF600C5C1BF /* WindSpeedPopUpVCTests.swift in Sources */,
+ C0FDAA752B0478BF00BAB570 /* UVView.swift in Sources */,
+ C0FDAA532AF21E8A00BAB570 /* iPadMainViewController.swift in Sources */,
+ C0FDAA792B04808200BAB570 /* PressureView.swift in Sources */,
+ C0FDAA572AF2C84E00BAB570 /* iPadMainTopCurrentStack.swift in Sources */,
+ C0160AB32B2A62A70016F9B0 /* SunsetSunriseView.swift in Sources */,
+ C0FDAA632AF832C100BAB570 /* HumidityStackView.swift in Sources */,
+ C0FDAA812B0C340800BAB570 /* iPadWeatherKitCallUtil.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -707,6 +843,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -767,6 +904,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
@@ -797,7 +935,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "programmatic-ui-weather-app/programmatic-ui-weather-app.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = PT5J4Z6J5K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "programmatic-ui-weather-app/Info.plist";
@@ -816,7 +954,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.6.6;
+ MARKETING_VERSION = 0.9.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.ES.weatherkit-programmatic-app";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -824,7 +962,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -836,7 +974,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "programmatic-ui-weather-app/programmatic-ui-weather-app.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = PT5J4Z6J5K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "programmatic-ui-weather-app/Info.plist";
@@ -855,7 +993,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 0.6.6;
+ MARKETING_VERSION = 0.9.1;
PRODUCT_BUNDLE_IDENTIFIER = "com.ES.weatherkit-programmatic-app";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -863,7 +1001,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
+ TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
diff --git a/weatherkit-weather-app.xcodeproj/project.xcworkspace/xcuserdata/RuslanS.xcuserdatad/UserInterfaceState.xcuserstate b/weatherkit-weather-app.xcodeproj/project.xcworkspace/xcuserdata/RuslanS.xcuserdatad/UserInterfaceState.xcuserstate
index 39cfb4f..d42b9ff 100644
Binary files a/weatherkit-weather-app.xcodeproj/project.xcworkspace/xcuserdata/RuslanS.xcuserdatad/UserInterfaceState.xcuserstate and b/weatherkit-weather-app.xcodeproj/project.xcworkspace/xcuserdata/RuslanS.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/weatherkit-weather-app.xcodeproj/xcuserdata/RuslanS.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/weatherkit-weather-app.xcodeproj/xcuserdata/RuslanS.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
index 8f36f14..948530b 100644
--- a/weatherkit-weather-app.xcodeproj/xcuserdata/RuslanS.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
+++ b/weatherkit-weather-app.xcodeproj/xcuserdata/RuslanS.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -176,8 +176,8 @@
filePath = "programmatic-ui-weather-app/Utils/WeatherKitCallUtil.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
- startingLineNumber = "126"
- endingLineNumber = "126"
+ startingLineNumber = "127"
+ endingLineNumber = "127"
landmarkName = "getWeather(location:)"
landmarkType = "7">
@@ -224,8 +224,8 @@
filePath = "programmatic-ui-weather-app/Utils/WeatherKitCallUtil.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
- startingLineNumber = "147"
- endingLineNumber = "147"
+ startingLineNumber = "149"
+ endingLineNumber = "149"
landmarkName = "getWeather(location:)"
landmarkType = "7">
@@ -258,9 +258,41 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "19"
endingLineNumber = "19"
- landmarkName = "MainViewController"
+ landmarkName = "iPhoneMainViewController"
landmarkType = "3">
+
+
+
+
+
+
+
+