NaN is schown when a calculation has no value inside. How can "0" or "-" be displayed instead in SwiftUI - string

If a calculation is performed in SwiftUI and no values are added to the Textfield it displays "-NaN" (Not a Nummber?) instead.
How can a value (for example "0", "-") be displayed until values are inserted?
I have tried adding #State private var display = 0 but it displayed both values...
Example code:
struct ContentView: View {
#EnvironmentObject var userData: UserData
var Brutto: Double{
let price = Double(userData.BPrice) ?? 0
let rent = Double(userData.Rent) ?? 0
let Brutto = rent * 12 / price * 100
return Brutto
}
var body: some View {
VStack {
Text ("\(Brutto, specifier: "%.1f")")
}
// The UserData File:
class UserData : ObservableObject {
private static let userDefaultBPrice = "BPrice"
private static let userDefaultRent = "Rent"
#Published var BuyingPrice = UserDefaults.standard.string(forKey: UserData.userDefaultBPrice) ?? ""
{
didSet {
UserDefaults.standard.set(self.BPrice, forKey: UserData.userDefaultBPrice)
}
}
#Published var Rent = UserDefaults.standard.string(forKey: UserData.userDefaultRent) ?? "" {
didSet {
UserDefaults.standard.set(self.Rent, forKey: UserData.userDefaultRent)
}
}
private var canc: AnyCancellable!
}
Thanks

Here is possible approach
VStack {
Text ("\(Brutto.isNaN ? 0 : Brutto, specifier: "%.1f")")
}

Related

multiply two value from textfield by using core data in swiftui

i try to make an invoice app by using core data, i have 4 attribute: Article String, Price String , Quantity String and Taxe String and i try to make a fonction to multiply price * quantity and the result of them i want to make another function for calculate the taxe for each article
Like this ->
Article:
Price $:
Quantity x:
Taxe %:
Total taxe %:
Total price taxe incl $:
and then at the end i want sum total price and total taxe of my list created by user in textfield in 2 diffrent textView
like this ->
Total taxe %:
Total prie incl taxe $:
struct Facturation: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(entity: FacturationCoreData.entity(), sortDescriptors: []) private var factures: FetchedResults<FacturationCoreData>
#State private var article = ""
#State private var prix = ""
#State private var quantite = ""
#State private var tvac = ""
#State private var tva = [0, 6, 12, 21]
#State private var tipIndex = 2
#AppStorage("textChange") private var textChange = ""
func prixQuantite() -> Double {
let fact = FacturationCoreData(context: viewContext)
let prix = fact.prix ?? ""
let quan = fact.quantite ?? ""
let sum = ((Double(prix) ?? 00) * (Double(quan) ?? 00))
return sum
}
func calculeTvaC() ->Double {
let fact = FacturationCoreData(context: viewContext)
let prixEtQuantite = prixQuantite()
let tva = fact.tva ?? ""
let prixTTC = ((prixEtQuantite / 100 * (Double(tva ?? "") ?? 00)) +
prixEtQuantite)
return prixTTC
}
var body: some View {
NavigationView{
VStack {
Form {
Section("Article"){
TextField("Article", text: $article)
.disableAutocorrection(true)
}
HStack{
Section("Q"){
TextField("Quantite", text: $quantite)
.keyboardType(.decimalPad)
}
Section("P") {
TextField("Prix", text: $prix)
.keyboardType(.decimalPad)
}
Section("T") {
TextField("TVA", text: $tvac)
.keyboardType(.decimalPad)
}
}
Button {
let facture = FacturationCoreData(context: viewContext)
facture.article = article
facture.quantite = quantite
facture.prix = prix
facture.tva = tvac
do{
try viewContext.save()
}catch{
print(error)
}
} label: {
Image(systemName: "square.and.arrow.down.fill")
.font(.title)
}
List{
ForEach(factures) { facture in
VStack(alignment: .leading){
Text("Artile: " + (facture.article ?? ""))
Text("Quantité: " + (facture.quantite ?? "") + "x")
Text("Prix: " + (facture.prix ?? "") + "€")
Text("Tva: " + (facture.tva ?? "") + "%")
Text("Total price:\(prixQuantite())$ ")
Text("Total taxe:\(calculeTvaC())$ ")
}.onTapGesture {
textChange = facture.article ?? ""
}
}.onDelete { indexSet in
indexSet.forEach { index in
let deletfacture = factures[index]
viewContext.delete(deletfacture)
do{
try viewContext.save()
}catch{
print(error)
}
}
}
}
}
Text(textChange)
}
.navigationTitle("Facturation")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink {
Client()
} label: {
Image(systemName: "person.badge.plus")
}
}
ToolbarItem(placement: .navigationBarLeading) {
NavigationLink {
ClientList()
} label: {
Image(systemName: "list.bullet")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
}
}
}
You can use an extension
extension FacturationCoreData{
var prixQuantite : Double {
let prix = self.prix ?? ""
let quan = self.quantite ?? ""
let sum = ((Double(prix) ?? 00) * (Double(quan) ?? 00))
return sum
}
var calculeTvaC : Double {
let prixEtQuantite = prixQuantite
let tva = self.tva ?? ""
let prixTTC = ((prixEtQuantite / 100 * (Double(tva ?? "") ?? 00)) +
prixEtQuantite)
return prixTTC
}
}
Then you can use
Text("Total price:\(facture.prixQuantite)$ ")
Text("Total taxe:\(facture.calculeTvaC)$ ")

My struct values cannot be used in function -> Cannot convert value of type 'Int' to expected argument type 'Range<Int>'

Im getting an error when using my Question struct in my function checkAnswer
I want to use the value in questions[index].answer but it error says
Cannot convert value of type 'Int' to expected argument type 'Range' and Value of type 'ArraySlice' has no member 'answer'
What did I do wrong on that part?
import SwiftUI
struct Question {
var question: String
var answer: Int
}
struct ContentView: View {
#State private var isGameRunning = false
let multiplyTableRange = Range(2...12)
#State private var selectedTable = 2
#State private var selectedNumOfQuestions = 5 //min of 5
#State private var variantsForCountOfQuestions = [5, 10, 20]
#State private var questions = [Question]()
#State private var currentQuestionIndex: Int = 1
#State private var answerInput: Int = 0
#State private var score = 0
#State private var isGameDone = false
#State private var gameCompleteMessage = ""
var body: some View {
if isGameRunning{
Group{
VStack{
Text("Your Score \(score)")
Text("\(questions[currentQuestionIndex].question)")
TextField("Enter your answer", value: $answerInput, formatter: NumberFormatter())
//error here
Button("Enter") {
checkAnswer(userAnswer: answerInput ?? 0 , answer: questions[currentQuestionIndex].answer)
}
.alert(isPresented: $isGameDone) {
Alert(title: Text(gameCompleteMessage), message: Text("Restart game"), primaryButton: .destructive(Text("Okay")) {
isGameRunning = false
answerInput = 0
score = 0
gameCompleteMessage = ""
currentQuestionIndex = 1
}, secondaryButton: .cancel())
}
}
}
}else{
Group{
VStack{
Text("Pick multiplication table to practice")
Picker("Pick multiplication table to practice", selection: $selectedTable){
ForEach(multiplyTableRange, id: \.self){
Text("\($0)")
}
} //eof picker
.labelsHidden()
.pickerStyle(SegmentedPickerStyle())
Text("How many questions?")
Picker("", selection: $selectedNumOfQuestions){
ForEach(variantsForCountOfQuestions, id: \.self){
Text("\($0)")
}
}//eofPicker
.labelsHidden()
.pickerStyle(SegmentedPickerStyle())
Button("Start"){
generateQuestions()
isGameRunning.toggle()
}
}
}
}
} //eof body
func generateQuestions(){
questions.removeAll()
for question in 1...selectedNumOfQuestions {
let x = Question(question: ("\(selectedTable) X \(question)"), answer: selectedTable * question)
questions.append(x)
}
}
func checkAnswer(userAnswer: Int, answer: Int) {
if userAnswer == answer {
score += 1
}
if currentQuestionIndex < selectedNumOfQuestions - 1 {
currentQuestionIndex += 1
} else {
gameCompleteMessage = "Your score is \(score)"
isGameDone = true
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Refresh a view when the children of an object are changed in SwiftUI

I am working on a CoreData application with two entities MyList and MyListItem. MyList can have many MyListItem (one to many). When the app is launched, I can see all the lists. I can tap on a list to go to the list items. On that screen, I tap a button to add an item to the selected list. After, adding the item when I go back to the all lists screen I cannot see the number of items reflected in the count. The reason is that MyListsView is not rendered again since the number of lists have not changed.
The complete code is shown below:
import SwiftUI
import CoreData
extension MyList {
static var all: NSFetchRequest<MyList> {
let request = MyList.fetchRequest()
request.sortDescriptors = []
return request
}
}
struct DetailView: View {
#Environment(\.managedObjectContext) var viewContext
let myList: MyList
var body: some View {
VStack {
Text("Detail View")
Button("Add List Item") {
let myListP = viewContext.object(with: myList.objectID) as! MyList
let myListItem = MyListItem(context: viewContext)
myListItem.name = randomString()
myListItem.myList = myListP
try? viewContext.save()
}
}
}
func randomString(length: Int = 8) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
}
class ViewModel: NSObject, ObservableObject {
#Published var myLists: [MyList] = []
private var fetchedResultsController: NSFetchedResultsController<MyList>
private(set) var context: NSManagedObjectContext
override init() {
self.context = CoreDataManager.shared.context
fetchedResultsController = NSFetchedResultsController(fetchRequest: MyList.all, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
super.init()
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
guard let myLists = fetchedResultsController.fetchedObjects else { return }
self.myLists = myLists
} catch {
print(error)
}
}
}
extension ViewModel: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
guard let myLists = controller.fetchedObjects as? [MyList] else { return }
self.myLists = myLists
}
}
struct MyListsView: View {
let myLists: [MyList]
var body: some View {
List(myLists) { myList in
NavigationLink {
DetailView(myList: myList)
} label: {
HStack {
Text(myList.name ?? "")
Spacer()
Text("\((myList.items ?? []).count)")
}
}
}
}
}
struct ContentView: View {
#StateObject private var vm = ViewModel()
#Environment(\.managedObjectContext) var viewContext
var body: some View {
NavigationView {
VStack {
// when adding an item to the list the MyListView view is
// not re-rendered
MyListsView(myLists: vm.myLists)
Button("Change List") {
}
}
}
}
func randomString(length: Int = 8) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
}
Inside ContentView there is a view called "MyListsView". That view is not rendered when the items are added. Since, according to that view nothing changed since the number of lists are still the same.
How do you solve this problem?
UPDATE:
What happens if I add one more level of views like for ListCellView as shown below:
struct MyListCellView: View {
#StateObject var vm: ListCellViewModel
init(vm: ListCellViewModel) {
_vm = StateObject(wrappedValue: vm)
}
var body: some View {
HStack {
Text(vm.name)
Spacer()
Text("\((vm.items).count)")
}
}
}
#MainActor
class ListCellViewModel: ObservableObject {
let myList: MyList
init(myList: MyList) {
self.myList = myList
self.name = myList.name ?? ""
self.items = myList.items!.allObjects as! [MyListItem]
print(self.items.count)
}
#Published var name: String = ""
#Published var items: [MyListItem] = []
}
struct MyListsView: View {
#StateObject var vm: ViewModel
init(vm: ViewModel) {
_vm = StateObject(wrappedValue: vm)
}
var body: some View {
let _ = Self._printChanges()
List(vm.myLists) { myList in
NavigationLink {
DetailView(myList: myList)
} label: {
MyListCellView(vm: ListCellViewModel(myList: myList))
}
}
}
}
Now the count is again not being updated.
Your ViewModel is an ObserveableObject, but you are not observing it in MyListsView. When you initialized MyListsView, you set a let constant. Of course that won't update. Do this instead:
struct MyListsView: View {
#ObservedObject var vm: ViewModel
init(viewModel: ViewModel) {
self.vm = viewModel
}
var body: some View {
List(vm.myLists) { myList in
NavigationLink {
DetailView(myList: myList)
} label: {
HStack {
Text(myList.name ?? "")
Spacer()
Text("\((myList.items ?? []).count)")
}
}
}
}
}
Now the #Published in ViewModel will cause MyListView to change when it does, and that includes adding a related entity.
We don't need MVVM in SwiftUI, the View data structs already fill that role and property wrappers make them behave like objects giving best of both worlds. In your case use the #FetchRequest property wrapper for the list and #ObservedObject for the detail and body will be called on any changes to the model data. Examine the code in the app template in Xcode with Core Data checked. It looks like this:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
DetailView(item: item)
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
...
struct DetailView: View {
#ObservedObject var item: Item
var body: some View {
Text("Item at \(item.timestamp!, formatter: itemFormatter)")

Convert address to coordinates using MKLocalSearchCompleter and CoreLocation

I have tried to make an app with a textfield to let user input a location, using MKLocalSearchCompleter to complete the searching. After that i would like to get the coordinate and display on the MapKit. However, I failed to get the coordinate using the Geocoder.
class LocationSearchService: NSObject, ObservableObject, MKLocalSearchCompleterDelegate {
#Published var searchQuery = ""
var completer: MKLocalSearchCompleter
#Published var completions: [MKLocalSearchCompletion] = []
var cancellable: AnyCancellable?
override init() {
completer = MKLocalSearchCompleter()
super.init()
cancellable = $searchQuery.assign(to: \.queryFragment, on: self.completer)
completer.delegate = self
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
self.completions = completer.results
}
}
The location manager as follows:
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
private let geocoder = CLGeocoder()
let objectWillChange = PassthroughSubject<Void, Never>()
#Published var status: CLAuthorizationStatus? {
willSet { objectWillChange.send() }
}
#Published var location: CLLocation? {
willSet { objectWillChange.send() }
}
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
#Published var placemark: CLPlacemark? {
willSet { objectWillChange.send() }
}
private func lookupLocation() {
guard let location = self.location else { return }
geocoder.reverseGeocodeLocation(location, completionHandler: { (places, error) in
if error == nil {
self.placemark = places?[0]
} else {
self.placemark = nil
}
})
}
// !!! This is the function I would like to use to get the Coordinate from the address obtained from LocationSearchService
func getCoordinate(address: String) {
geocoder.geocodeAddressString(address, completionHandler: { (places, error) in
if error == nil {
self.placemark = places?[0]
self.location = self.placemark?.location
} else {
self.placemark = nil
self.location = nil
}
})
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.status = status
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return } //.first or .last?
self.location = location
self.lookupLocation()
}
}
Content View like this:
struct ContentView: View {
#State private var location: String = ""
#ObservedObject var lm = LocationManager()
private let completer = MKLocalSearchCompleter()
#ObservedObject var locationSearchService = LocationSearchService()
var body: some View {
NavigationView {
VStack {
AddressSearchBar(text: $locationSearchService.searchQuery)
List(locationSearchService.completions, id: \.self) { completion in
VStack(alignment: .leading) {
Text(completion.title)
// Error here, I cannot translate the address to location
//Text(lm.getCoordinate(address: completion.title))
}
}.navigationTitle("Search Location")
}
}
A few issues here:
I would like to convert the user selected item (which I failed to implement here) to the address (completion.title) -- i.e., need to get user selection on the suggested item.
I would like to convert the address found in the suggestion to a coordinate, so that I can mark on MapView.

SwiftUI + Core Data - updating an object (Detail -> DetailEdit)

Goal: update a core data object with SwiftUI: DetailView -> EditDetail -> DetailView (updated).
Problem: code bellow works, but creates a new object, instead of updating existing one.
import SwiftUI
struct DetailView: View {
var order = Order()
#State var showOrderEdit = false
var body: some View {
Form{
Text(order.tableNumber)
Text(order.pizzaType)
}
.navigationTitle(order.pizzaType)
.toolbar {
ToolbarItem(placement: .primaryAction) {
//edit button
Button(action: {
showOrderEdit = true
}, label: {
Text("Edit")
})
.sheet(isPresented: $showOrderEdit) {
OrderEdit(order: order)
}
}
}
}
}
import SwiftUI
struct DetailEdit: View {
#State var tableNumber = ""
#Environment(\.managedObjectContext) private var viewContext
#Environment (\.presentationMode) var presentationMode
var order = Order()
var body: some View {
NavigationView {
Form {
TextField("table number", text: $tableNumber)
//update button
Button(action: {
updateOrder(order: order)
}) {
Text("Update")
.foregroundColor(.blue)
}
}
//passing data item detail -> item edit
.onAppear {
self.tableNumber = self.order.tableNumber
}
.navigationTitle("Edit Order")
}
}
func updateOrder(order: Order) {
let newtableNumber = tableNumber
viewContext.performAndWait {
order.tableNumber = newtableNumber
try? viewContext.save()
}
}
You create new Order object in each view, so it is stored as new one into database. Instead you need to inject CoreData object from parent view (which shows DetailView) as observed object,
struct DetailView: View {
#ObservedObject var order: Order // << here !!
// .. other code
and
struct DetailEdit: View {
#State var tableNumber = ""
#Environment(\.managedObjectContext) private var viewContext
#Environment (\.presentationMode) var presentationMode
#ObservedObject var order: Order // << here !!
// ... other code
in such approach you will work with same instance of Order in both views and they will be updated because observe that instance for modifications.

Resources