Need both Celsius and Fahrenheit with WeatherKit - swiftui

I have a weather app that tells the temperature in Celsius and Fahrenheit at the same time. I'd like to use WeatherKit, but I'm having trouble rounding Celsius down to no decimal places. I can do it with Fahrenheit with .formatted because I'm based in the US, but I can't round down Celsius at the same time. Is there an easy way to do that in SwiftUI? Or is it possible to manually set the locale for just a single property?
if let weather{
let celsiusWeather = weather.currentWeather.temperature.converted(to: .celsius).description
VStack{
Text("New York")
.font(.largeTitle)
Text("\(weather.currentWeather.temperature.converted(to: .fahrenheit).formatted().description)")
Text(celsiusWeather)
}
}
}
This current code comes up with it half there:
New York
45°F
5.98°C
But I would like it simply be 6°C instead. I've tried string interpolation:
let celsiusFormatted = String(format: "%.0f", celsiusWeather)
and that just came up with 0, not even any of the temperature, so I'm not sure if because it's from WeatherKit that it can do that or not.
Any help would be great. Let me know if you need more code to help clarify.

Try
let celsiusFormatted = String(format: "%.0f", celsiusWeather.value)
celsiusWeather appears to be a measurement not a Double the value of a measurement is a Double.
You can also include precision for a Text
Text(weather.currentWeather.temperature.converted(to: .celsius), format: .measurement(width: .abbreviated, usage: .asProvided,numberFormatStyle: .number.precision(.fractionLength(2))))

You can format the temperature using MeasurementFormatter. Remember to set numberFormatter.maximumFractionDigits = 0 to round the number as required and unitOptions = .providedUnit to ensure the correct units are displayed.
struct ContentView: View {
let temp: Measurement<UnitTemperature>
var body: some View {
VStack {
HStack {
Text("New York")
Text(temp, formatter: formatter)
Text(temp.converted(to: .celsius), formatter: formatter)
}
}
}
var formatter: MeasurementFormatter {
let formatter = MeasurementFormatter()
formatter.unitStyle = .medium
formatter.numberFormatter.maximumFractionDigits = 0
formatter.unitOptions = .providedUnit
return formatter
}
}

Related

Swift Charts - changing the X axis literals

I am moving my charts over from Daniel Gindi to Apple's Chart and haven't worked out how to control the X axis labels.
The X axis generates a daily angle from day 1 to 365 for the year, so is an integer. I want to display the months of the year, as shown in the previous version of the chart.
Is it possible to use .chartXScale for this purpose? I am not sure how to get it to accept strings or if I need to try a different approach.
var monthValues: [String] = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"]
Chart{
ForEach(dayTilt) {item in
LineMark(
...
)
}
.frame(height: 360)
.chartXScale(domain: monthValues[0...11])
You should probably store a Date rather than an Int for a day, so something like:
struct DayTilt: Identifiable {
internal init(day: Int, value: Float) {
date = Calendar.current.date(from: DateComponents(day: day))!
self.value = value
}
let id = UUID()
let date: Date
let value: Float
}
then you can use the following PlottableValue
value(_:date:unit:calendar:)
e.g.
struct ContentView: View {
#State var dayTilts: [DayTilt] = …
var body: some View {
Chart(dayTilts) { dayTilt in
LineMark(x: .value("Date", dayTilt.date, unit: .day),
y: .value("Angle", dayTilt.value))
}
.padding()
}
}

Wrapping multiline text inside a shape in SwiftUI

As the title states, I am looking for a way to wrap multiline text inside of a shape in SwiftUI (see image below). I tried using .clipToShape(MyShape()), but that just renders any text not within the shape invisible.
I have accomplished this in the past using UIKit and exclusion paths, but would like to find a way to achieve the same effect with SwiftUI.
Any advice would be greatly appreciated.
I found a numeric way to do so, but it is probably not the best one.
It should work on IOS and macOS. I tested it on macOS with swiftUI.
The first thing we do is to find out how many rectangles with the hight of the font size are fitting in the circle with its diameter. Then we figure their width out. The last step is grabbing for each rectangle the amount of characters fitting in and adding them into an array. By converting the hole array back to a String we add after each rectangle an "\n" to get the correct multiline alignment.
func createCircularText(text: String, verticalSpacing: Double, circleDiameter: Double, FontSize: Double) -> String {
var Text = text
var circularText = String()
var CountOfWordLines = Int()
var widthOfWordLine = [Int]()
var widthOfWordLineSorted = [Int]()
var array = [String]()
let heigthOfWordLines = FontSize + verticalSpacing
var Dnum = (((1/heigthOfWordLines) * circleDiameter) - 2.0)
Dnum.round(.up)
CountOfWordLines = Int(Dnum)
for n in 1...(CountOfWordLines / 2) {
let num0 = circleDiameter / 2.0
let num1 = pow(num0, 2.0)
let num2 = (Double(n) * heigthOfWordLines)
let num3 = pow(num2,2.0)
let num4 = num1 - num3
let num5 = sqrt(Double(num4))
let num = Int((num5 / 10) * 3)
widthOfWordLine.append(Int(num))
}
widthOfWordLineSorted.append(contentsOf: widthOfWordLine.sorted { $1 > $0 })
widthOfWordLine.removeFirst()
widthOfWordLineSorted.append(contentsOf: widthOfWordLine)
widthOfWordLine.removeAll()
for n in widthOfWordLineSorted {
array.append(String(Text.prefix(n)))
if Text.isEmpty {} else {
let t = Text.dropFirst(n)
Text = String(t)
}
}
circularText = array.joined(separator: "\n")
return circularText
}
In our view we embed the function like this:
#State var text = "your text"
#State var CircularText = String()
// body:
ZStack {
Circle().frame(width: 200)
Text(CircularText)
.multilineTextAlignment(.center)
}
.onAppear(perform: {
CircularText = createCircularText(text: text, verticalSpacing: 3.0, circleDiameter: 200, FontSize: 12)
})
I just tested it with the font size 12, but it should perform with any other as well quite ok.
By changing the diameter you will notice that the text becomes a bit oval, to fix that please change the verticalSpacing. As smaller the number gets, as taller the circle gets, and the other way around. But feel free to fix that issue.
Also, please make sure that your text is long enough.

Dynamically Combining Currency Symbol to TextField Text

Is it possible to dynamically combine a currency symbol to some text before displaying it via TextField on a single text line? Currently my currency symbol is on the line above the TextField text "Enter Amount > "
This code produces the errors: "Cannot assign to property: 'self' is immutable" and "Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols."
I would like to use something like this in a form.
struct ContentView: View {
#State private var moneyS: String = ""
var curr: String = ""
var curSymb: Int = 2
var mxx: String = ""
var body: some View {
Text("Blah Blah Blah")
switch curSymb {
case 1:
curr = "$"
case 2:
curr = "€"
default:
curr = "£"
}
mxx = "Enter Amount > " + curr
TextField(mxx, text: $moneyS)
.keyboardType(.decimalPad)
}
}
Yes, it certainly is possible. By default, it uses VStack-like layout and places all the views vertically. We can use HStack (horizontal stack) to align your text views horizontally.
Here is the updated version:
struct ContentView: View {
...
var curr: String {
switch curSymb {
case 1: return "$"
case 2: return "€"
default: return "£"
}
}
var body: some View {
HStack(alignment: .firstTextBaseline) { // note that we use firstTextBaseline in order to keep the text aligned even if you decided to have different font for every part of the text
Text("Blah Blah Blah")
TextField("Enter Amount > " + curr, text: $moneyS)
.keyboardType(.decimalPad)
}
}
}
Also, note, that I have also moved the curr calculation to a variable so that the body code stays small.
Thanks for the assistance. I like the concise compact code.
Sorry for not being clear about the blah blah blah line. There are other lines of text included in a VStack but just "Enter Amount > " and the currency symbol in the HStack.
.currency(code: "INR")
You can set country code dynamically.
struct ExchangeRateView: View {
#State var currencyFromInput:Double = 0.1
var body: some View {
TextField("Enter Amount", value: $currencyFromInput, format: .currency(code: "INR"))
}
}

What is the best way to get Drag Velocity?

I was wondering how can one get DragGesture Velocity?
I understand the formula works and how to manually get it but when I do so it is no where what Apple returns (at least some times its very different).
I have the following code snippet
struct SecondView: View {
#State private var lastValue: DragGesture.Value?
private var dragGesture: some Gesture {
DragGesture()
.onChanged { (value) in
self.lastValue = value
}
.onEnded { (value) in
if lastValue = self.lastValue {
let timeDiff = value.time.timeIntervalSince(lastValue.time)
print("Actual \(value)") // <- A
print("Calculated: \((value.translation.height - lastValue.translation.height)/timeDiff)") // <- B
}
}
var body: some View {
Color.red
.frame(width: 50, height: 50)
.gesture(self.dragGesture)
}
}
From above:
A will output something like Value(time: 2001-01-02 16:37:14 +0000, location: (250.0, -111.0), startLocation: (249.66665649414062, 71.0), velocity: SwiftUI._Velocity<__C.CGSize>(valuePerSecond: (163.23212105439427, 71.91841849340494)))
B will output something like Calculated: 287.6736739736197
Note from A I am looking at the 2nd value in valuePerSecond which is the y velocity.
Depending on how you drag, the results will be either different or the same. Apple provides the velocity as a property just like .startLocation and .endLocation but unfortunately there is no way for me to access it (at least none that I know) so I have to calculate it myself, theoretically my calculations are correct but they are very different from Apple. So what is the problem here?
This is another take on extracting the velocity from DragGesture.Value. It’s a bit more robust than parsing the debug description as suggested in the other answer but still has the potential to break.
import SwiftUI
extension DragGesture.Value {
/// The current drag velocity.
///
/// While the velocity value is contained in the value, it is not publicly available and we
/// have to apply tricks to retrieve it. The following code accesses the underlying value via
/// the `Mirror` type.
internal var velocity: CGSize {
let valueMirror = Mirror(reflecting: self)
for valueChild in valueMirror.children {
if valueChild.label == "velocity" {
let velocityMirror = Mirror(reflecting: valueChild.value)
for velocityChild in velocityMirror.children {
if velocityChild.label == "valuePerSecond" {
if let velocity = velocityChild.value as? CGSize {
return velocity
}
}
}
}
}
fatalError("Unable to retrieve velocity from \(Self.self)")
}
}
Just like this:
let sss = "\(value)"
//Intercept string
let start = sss.range(of: "valuePerSecond: (")
let end = sss.range(of: ")))")
let arr = String(sss[(start!.upperBound)..<(end!.lowerBound)]).components(separatedBy: ",")
print(Double(arr.first!)!)

SwiftUI display yesterday's date

How do I list yesterday's date in SwiftUI? It probably is a simple answer but I'm just learning to code and for some reason I can't seem to find the solution anywhere. Is it because it is too easy?
struct DateShown: View {
let datechoice: Datechoice
var body: some View {
Text(currentDate(date: Date()))
.font(.headline)
.fontWeight(.bold)
.foregroundColor(.blue)
}
func currentDate(date: Date!) -> String {
let formatter = DateFormatter()
formatter.locale = .current
formatter.dateFormat = "MMMM d, yyyy"
return date == nil ? "" : formatter.string(from: date)
}
}
I would rather use View extensions, though you also need Date formatting so I went the easier way and extended your solution. If the number at line "dayComponent.day" is positive, you go futher in time. I tested under:
swift 5
xcode 11.3.1
iOS 13.3.1 non beta
func yesterDay() -> String {
var dayComponent = DateComponents()
dayComponent.day = -1
let calendar = Calendar.current
let nextDay = calendar.date(byAdding: dayComponent, to: Date())!
let formatter = DateFormatter()
formatter.locale = .current
formatter.dateFormat = "MMMM d, yyyy"
return formatter.string(from: nextDay). //Output is "March 6, 2020
}
Usage is the same as yours:
Text(yesterDay())