Error "No exact matches in call to initializer" - swift

I am a beginner developer and I want to add my price variable which is a double next to the title variable in my view. When i try Text(price) is is giving me the error "No exact matches in call to initializer". Is this because I cannot use a double inside a Text?
import SwiftUI
struct TaskRow: View {
var task: String
var price: Double
var completed: Bool
var body: some View {
HStack(spacing: 20) {
Image(systemName: completed ?
"checkmark.circle" : "circle")
Text(price) "No exact matches in call to initializer"
Text(task)
}
}
}
struct TaskRow_Previews: PreviewProvider {
static var previews: some View {
TaskRow(task: "Do laundry", price: 1.00, completed: true)
}
}
Screenshot of issue:

The issue is Text takes in a String, so you just need to convert the Double to a String first. You can do this if you want to plan for locale or different currencies
import SwiftUI
struct TaskRow: View {
var task: String
var price: Double
var completed: Bool
var priceString: String {
return price.toLocalCurrency()
}
var body: some View {
HStack(spacing: 20) {
Image(systemName: completed ?
"checkmark.circle" : "circle")
Text(priceString)
Text(task)
}
}
}
struct TaskRow_Previews: PreviewProvider {
static var previews: some View {
TaskRow(task: "Do laundry", price: 1.00, completed: true)
}
}
// This could be in another file
// Extension to convert doubles to currency strings consistently
extension Double {
func toLocalCurrency() -> String {
let formatter = NumberFormatter()
var priceString = self.description
formatter.locale = Locale.current
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: NSNumber(value: self)) {
priceString = formattedTipAmount
}
return priceString
}
}
You could also use any of these if you don't want to bother with Locale for now:
Text("\(price)") // 1.00
Text(price.description) // 1.00
Text("$\(price)") // $1.00

Related

how to update struct variable when swiped in TabView

I want to record long term, how many times a specific ItemView has been displayed in my TabView below. Each time a user swipes on the tab, I want to update var timesViewed by 1. However, timesViewed doesn't seem to update and I am really stuck as to why now.
I removed some view modifiers to simplify the code below.
struct Item: Identifiable, Hashable, Codable {
var id = UUID()
var title: String
var detail: String
var repeatOn: String
var timesViewed = 0
mutating func viewedThisItem() {
timesViewed += 1
}
}
struct ItemSessionView: View {
var itemViewModel: ItemListVM
#State var count = 0
#State var currentIndex = 0
var body: some View {
let today = getTodaysDate().uppercased()
var tempList = itemViewModel.list.filter({ return $0.repeatOn == today})
ZStack {
GeometryReader { proxy in
TabView(selection: $currentIndex) {
ForEach(tempList) { item in
Group {
if today == item.repeatOn {
ItemDetailView(item: item)
}
}
}
}
.onChange(of: currentIndex) { value in
tempList[currentIndex].viewedThisItem()
}
}
}
}
func getTodaysDate() -> String {
let today = Date.now
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
formatter.dateFormat = "E"
let todaysDate = formatter.string(from: today)
return todaysDate
}
}
Structs are value type, you modify the (copied) item in the filtered array but not the original item in the itemViewModel object.
A possible solution is to get the item in the itemViewModel object by id and modify that directly.
.onChange(of: currentIndex) { value in
let id = tempList[value].id
let index = itemViewModel.list.firstIndex{$0.id == id}!
itemViewModel.list[index].viewedThisItem()
}
Force unwrapping is safe because the item does exist.

I can't pass data by using #Binding from one View to other

i created a weather app, which download current weather data by city name (user type it) and put this data inside a list - it work's. But i also created WeatherDetailView() which, should show a detailed data like, hourly forecast for current location. Unfortunately it's impossible to download this data by using city name, so i decided to use Latitude and Longitude, from first View - it should work like this: user type a city name, the city appear in list with temperature and icon (works) and if the user decide to go to next view by using NavigationLink, detailed data is downloaded by using lon&lat which is assigned to this city, and show detail data in next view. I have no idea how to do it, there is an error "Cannot find '$weatherElement' in scope" i tried many things and just can figure it out.
FirstView()
import SwiftUI
struct ContentView: View {
#ObservedObject var weatherVM : WeatherViewModel
init(){
self.weatherVM = WeatherViewModel()
}
var body: some View {
NavigationView{
List{
TextField("Add city...", text: self.$weatherVM.city){
weatherVM.search()
}
.padding(6)
ForEach(weatherVM.weatherList) { weatherElement in
WeatherDetailView(lat: $weatherElement.lat , lon:
$weatherElement.lon) // error Cannot find '$weatherElement' in scope
NavigationLink {
} label: ...
DetailViewModel()
import SwiftUI
struct WeatherDetailView: View {
#ObservedObject var detailVM : DetailViewModel
#Binding var lat : Double
#Binding var lon : Double
init(lat : Binding<Double>, lon : Binding<Double>){
self.detailVM = DetailViewModel()
self._lat = lat
self._lon = lon
}
let weather = WeatherDetails.addWeather()
var body: some View {
VStack(spacing: 30){
//... some ui code
ScrollView(.horizontal){ // here i want to put detail data
LazyHStack(spacing: 10){
ForEach(self.detailVM.detailsList, id: \.hours) { detail in
VStack{
Text(detail.hours)
Image(systemName: detail.icon)
Text(detail.temperature)
}
}
}
}
}.padding(.top, 10)
.onAppear {
detailVM.starUpdate(lat: 2.0 , lon: 5.0)// <- here i use #Binding values
}
}
}
WeatherModelView:
import Foundation
class WeatherViewModel : ObservableObject{
private var weatherService : WeatherService!
#Published var weather = WeatherModel()
struct weatherDetails : Identifiable {
let id = UUID()
let cityName : String
let temperature : String
let icon : String
let lat : Double
let lon : Double
}
#Published var weatherList = [weatherDetails(cityName: "London", temperature: "12", icon: "cloud.fill", lat: 2.0, lon: 3.0)]
func addCity(){
weatherList.removeAll()
if tempereture != "" && conditionName != "" {
weatherList.append(weatherDetails(cityName: city, temperature: tempereture, icon: conditionName, lat: weather.coord!.lat!, lon: weather.coord!.lon!))
} else {
print("no such city")
}
}
init(){
self.weatherService = WeatherService()
}
var tempereture : String {
if let temp = self.weather.main?.temp{
return String(format: "%.2f", temp)
} else {
return ""
}
}
var conditionName: String {
if let id = self.weather.weather?[0].id{
switch id {
case 200...232:
return "cloud.bolt"
case 300...321:
return "cloud.drizzle"
case 500...531:
return "cloud.rain"
case 600...622:
return "cloud.snow"
case 701...781:
return "cloud.fog"
case 800:
return "sun.max"
case 801...804:
return "cloud.bolt"
default:
return "cloud"
}
} else {
return ""
}
}
private func fetchWeather(by city: String){
self.weatherService.getWeather(city: city) {weather in
if let weather = weather {
DispatchQueue.main.async {
self.weather = weather
}
self.addCity()
}
}
}
var city : String = ""
func search(){
if let city = city.self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed){
fetchWeather(by: city)
}
}
}
WeatherDetailView:
import Foundation
import SwiftUI
class DetailViewModel : ObservableObject{
private var detailedWeatherService : DetailedWeatherService!
#Published var details = WeatherDetailModel()
#Published var detailsList = [Details(hours: "12:00", temperature: "13", icon: "cloud.fill")]
struct Details : Identifiable {
let id = UUID()
let hours : String
let temperature : String
let icon : String
}
init(){
self.detailedWeatherService = DetailedWeatherService()
}
private func getHour(dt : Int) -> String{
let date = NSDate(timeIntervalSince1970: TimeInterval(dt))
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date as Date)
return "\(hour):00"
}
private func getTemperature(temp : Double) -> String{
return String(format: "%.1f", temp)
}
private func getIcon(icon : Int) -> String{
switch icon{
case 200...232:
return "cloud.bolt"
case 300...321:
return "cloud.drizzle"
case 500...531:
return "cloud.rain"
case 600...622:
return "cloud.snow"
case 701...781:
return "cloud.fog"
case 800:
return "sun.max"
case 801...804:
return "cloud.bolt"
default:
return "cloud"
}
}
private func fullFillDetails(){
detailsList.removeAll()
if details.hourly != nil{
for i in 0..<24{
let detail = details.hourly![i]
detailsList.append(Details(hours: getHour(dt: detail.dt!), temperature: getTemperature(temp: detail.temp!), icon: getIcon(icon: detail.weather![0].id!)))
}
} else{
print("no data")
}
}
private func updateData(lat : Double, lon: Double){
self.detailedWeatherService.getDetails(lat : lat, lon: lon) { details in
if let details = details {
DispatchQueue.main.async {
self.details = details
self.fullFillDetails()
}
}
}
}
func starUpdate(lat: Double, lon : Double){ // rozpoczecie z View
updateData(lat: lat, lon: lon)
}
}
thank you.
I figured out!
ContentView()
...
NavigationLink {
WeatherDetailView(element: weatherElement) // <- here
} label: { ...
WeatherDetailView()
import SwiftUI
struct WeatherDetailView: View {
#ObservedObject var detailVM : DetailViewModel
#ObservedObject var weatherVM : WeatherViewModel
var element : WeatherViewModel.weatherDetails
init(element: WeatherViewModel.weatherDetails){
self.detailVM = DetailViewModel()
self.weatherVM = WeatherViewModel()
self.element = element
}
thanks for help, i post it here, it may help somebody.
The problem is that you're trying to create bindings to variables that aren't held in your master view, of course you couldn't access $weatherElement because it doesn't have a weatherElement to begin with.
Your detail view should accept an #ObservedObject so you can call it like this in the ForEach:
WeatherDetailView(weatherElement)
And in your WeatherDetailView:
struct WeatherDetailView: View {
#ObservedObject var detailVM : DetailViewModel
#ObservedObject var weatherDetail: WeatherDetails
init(_ details: WeatherDetails){
self.detailVM = DetailViewModel()
self._weatherDetails = details
}
That should fix it !

Swift NumberFormatter if value is 0 return empty string

I have the following code:
struct Food {
var name: String
var energy: Float = 0
var water: Float = 0
}
struct FoodItemView: View {
#State var newFoodItem = Food(name: "")
private var numberFormatter = NumberFormatter()
var body: some View {
Form {
Section(header: Text("Basics").fontWeight(.bold)) {
TextField("Name", text: $newFoodItem.name)
TextField("Energy (C)", value: $newFoodItem.energy, formatter: numberFormatter)
.keyboardType(.numberPad)
TextField("Water (g)", value: $newFoodItem.water, formatter: numberFormatter)
}
}
}
How do I make it so that, the value displayed in my TextFields would be an empty string if the bound value is 0, rather than explicitly displaying the 0?
Set .zeroSymbol property of NumberFormatter.
struct FoodItemView: View {
#State var newFoodItem = Food(name: "")
private var numberFormatter = NumberFormatter()
init() {
numberFormatter.zeroSymbol = "" //<< Here
}
// Other Code

How to get appropriate decimal/double currency value with decimal point?

How do I get to the proper value of the the amount entered in textfield? Assuming my dollar value is 50.05, I noticed that when I try to access:
bindingManager.text.decimal
I get 5005. What am I doing wrong to not get 50.05?
import SwiftUI
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContentView: View {
#ObservedObject private var bindingManager = TextBindingManager(amount: 0)
var decimal: Decimal { bindingManager.text.decimal / pow(10, Formatter.currency.maximumFractionDigits) }
var maximum: Decimal = 999_999_999.99
#State private var lastValue: String = ""
#State private var locale: Locale = .current {
didSet { Formatter.currency.locale = locale }
}
var body: some View {
VStack(alignment: .leading) {
TextField(bindingManager.text, text: $bindingManager.text)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing) // this will keep the text aligned to the right
.onChange(of: bindingManager.text) { string in
if string.decimal > maximum {
self.bindingManager.text = lastValue
} else {
self.bindingManager.text = decimal.currency
lastValue = self.bindingManager.text
}
return
}
}
.padding()
.onAppear {
Formatter.currency.locale = locale
}
}
}
class TextBindingManager: ObservableObject {
#Published var text: String = ""
var amount: Decimal = .zero
init(amount: Decimal) {
self.amount = amount
self.text = Formatter.currency.string(for: amount) ?? "$0.00"
}
}
fileprivate extension Formatter {
static let currency: NumberFormatter = .init(numberStyle: .currency)
}
extension NumberFormatter {
convenience init(numberStyle: Style) {
self.init()
self.numberStyle = numberStyle
}
}
extension StringProtocol where Self: RangeReplaceableCollection {
var digits: Self { filter (\.isWholeNumber) }
}
extension String {
var decimal: Decimal { Decimal(string: digits) ?? 0 }
}
extension Decimal {
var currency: String { Formatter.currency.string(for: self) ?? "" }
}
You just need to divide the decimal value by the number of maximum fraction digits. Same as it is being done with the decimal instance property of your ContentView:
var decimal: Decimal { bindingManager.text.decimal / pow(10, Formatter.currency.maximumFractionDigits) }
.onChange(of: bindingManager.text) { string in
if string.decimal > maximum {
self.bindingManager.text = lastValue
} else {
self.bindingManager.text = decimal.currency
lastValue = self.bindingManager.text
}
print("decimal", decimal)
return
}
This will print
decimal 0.05
decimal 0.5
decimal 5
decimal 50.05

Model to search notes by date

How to model notes, so I can search them ,after, by dates? like on the picture.
Firstly, I thought about dictionary with key of type Date; but it didn't work to me.
So I've tried the following String format:
"04-28-2020" - US calendar
"28-04-2020" - Other calendar
One of the issues - if the telephone changes date style(region dependent), keys remain in dictionary, but aren't accessed any more...
My intuition that it should be the dictionary, but what are the sustainable way to make such a model?
Can it be an array of notes [Note], without keys?
struct Note: Codable, Identifiable {
let id = UUID()
var date: Date
var title: String
var description: String
var dateString: String {
let df = DateFormatter()
df.dateStyle = .short
df.timeStyle = .medium
return df.string(from: date)
}
// method returns "key" to be used in dictionary
static func dateKey(for date: Date) -> String {
let df = DateFormatter()
df.dateStyle = .short
return df.string(from: date)
}
}
class NotesOrganizer: ObservableObject {
// #Published var notes = [Note]() - tried initially
#Published var notesDictionary = [String : [Note]]()
}
struct ContentView: View {
#State private var title = ""
#State private var description = ""
#ObservedObject var notesOrganizer = NotesOrganizer()
var body: some View {
NavigationView {
VStack {
Button("Save note") {
let key = Note.dateKey(for: Date())
notesOrganizer.notesDictionary[key]?.append(Note(date: Date(), title: title, description: description))
?? (notesOrganizer.notesDictionary[key] = [Note(date: Date(), title: title, description: description)])
print("Date Key: \(Note.dateKey(for: Date()))")
}
NavigationLink(destination: DetailView(notesOrganizer: notesOrganizer))
{
Text("Log history ->")
.foregroundColor(.purple)
.underline()
}
}
.navigationBarHidden(true)
}
}
init() {
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
UINavigationBar.appearance().shadowImage = UIImage()
}
}
DetailView()
extension Date {
var tomorrow: Date? {
return Calendar.current.date(byAdding: .day, value: 1, to: self)
}
var yesterday: Date? {
return Calendar.current.date(byAdding: .day, value: -1, to: self)
}
}
struct DetailView: View {
#ObservedObject var notesOrganizer: NotesOrganizer
#State private var dayLabel: String = Note.dateKey(for: Date())
#State private var chosenDate = Date()
var body: some View {
VStack {
HStack {
Button(action: {
chosenDate = chosenDate.yesterday!
dayLabel = Note.dateKey(for: chosenDate)
}, label: {
Image(systemName: "chevron.left")
})
Text(dayLabel)
Spacer()
Button(action: {
chosenDate = chosenDate.tomorrow!
dayLabel = Note.dateKey(for: chosenDate)
}, label: {
Image(systemName: "chevron.right")
})
}
.padding([.horizontal,.bottom])
List{
ForEach(notesOrganizer.notesDictionary[day] ?? []) { note in
HStack {
VStack(alignment:.leading) {
Text(note.title)
Text(note.description)
}.multilineTextAlignment(.leading)
Spacer()
Text(note.dateString)
}
}.onDelete(perform: removeItems)
}
.navigationBarTitle("", displayMode: .inline)
Spacer()
}