SwifUI : pass a stored value (via user defaults) to another view - swift

I used user defaults to save the values from a form. I wish to pass one of those values ("nationality") to another view.
I tried using the view model in order to do so, but I get an empty value in the text where I wish to pass this data.
first here is a look at the form
import SwiftUI
struct ProfilePageView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
Form {
Section(header: Text("Personal Ifo")) {
TextField("User Name", text: $viewModel.userName)
}
//
Section(header: Text("Your Nationality")) {
Picker(selection: $viewModel.nationality, label: Text("You were born in")) {
ForEach(viewModel.nationalities, id: \.self) { nationality in
Text(nationality)
}
}
} //nationality section end
//
Section(header: Text("Country of residence")) {
Picker(selection: $viewModel.countryOfResidence, label: Text("Where do you live ?")) {
ForEach(viewModel.countriesOfResidence, id: \.self) { residence in
Text(residence)
}
}
} // country of residence section end
//
}
.navigationTitle("Profile")
//form end
}
}
second here is my view model, which enables me to save the form values to user default
import Foundation
class ViewModel: ObservableObject {
#Published var userName: String {
didSet {
UserDefaults.standard.setValue(userName, forKey: "username")
}
}
//nationality
#Published var nationality: String {
didSet {
UserDefaults.standard.setValue(nationality, forKey: "nationality")
}
}
public var nationalities = ["USA", "Japan", "Senegal", "France"]
//country of residence
#Published var countryOfResidence: String {
didSet {
UserDefaults.standard.setValue(countryOfResidence, forKey: "country of residence")
}
}
public var countriesOfResidence = ["USA", "Japan", "Senegal", "France"]
init() {
self.userName = UserDefaults.standard.object(forKey: "username") as? String ?? ""
self.nationality = UserDefaults.standard.object(forKey: "nationality") as? String ?? ""
self.countryOfResidence = UserDefaults.standard.object(forKey: "Coutry of residence") as? String ?? ""
}
}
finally here is the view where I wish to attach this value. I am using #observedObject in order to try an retrieve the "nationality" value from my view model. however the value is not passed in the text
import SwiftUI
struct VisaPolicyDetailView: View {
#Binding var country: Country
#ObservedObject var viewmodel = ViewModel()
var body: some View {
VStack(alignment: .leading,spacing: 20) {
//a dismiss button
HStack {
Spacer()
Image(systemName: "xmark")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
}
Spacer()
Text(country.countryName)
.font(.title)
.fontWeight(.medium)
Text("Visa policy for \(viewmodel.nationality) : visa policy")
.font(.title2)
Spacer()
}
.padding()
.navigationTitle("Visa Policy Detail")
}
}

In the provided scenario it is better to use shared ViewModel, like
class ViewModel: ObservableObject {
static let shared = ViewModel()
// .. other code
}
and use that shared in all affected views
struct ProfilePageView: View {
#ObservedObject var viewModel = ViewModel.shared // << here !!
...
and
struct VisaPolicyDetailView: View {
#Binding var country: Country
#ObservedObject var viewmodel = ViewModel.shared // << here !!
...
then both views will observe same instance of ViewModel.
SwiftUI 2.0: use AppStorage to automatically store/observe user defaults, like
#AppStorage("nationality") var nationality = "some default here"

Related

how to update and refresh the view immediately after clicking the save button

This page has already can save the new information but it has to exit the page to the last page then this page can show up the new information after entering this page again.The main question is I want to update and refresh the information immediately after clicking the save button.
But I don't know how to do it.
The main page I talk about is here
import SwiftUI
struct profile3: View {
#State private var name = ""
#State private var instagram = ""
#State private var age = ""
#State private var birth = ""
#State private var habit = ""
#State private var live = ""
#State private var head = ""
#ObservedObject var httpClient = HTTPUser()
//let user: UserLogin
private func newPer(){
let new = self.httpClient.per
for value in new!{
let per = Per(id:value.id,name: name, account: instagram, picture: head, age: age, birth: birth, city: live, hobby: habit, user: value.user)
httpClient.newPer(per: per){success in
}
}
}
private func get(){
let defaults = UserDefaults.standard
guard let token = defaults.string(forKey: "value") else {
return
}
httpClient.getper(value: token)
}
var body: some View {
NavigationView{
ZStack{
Image("background")
.resizable()
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity)
.edgesIgnoringSafeArea(.all)
if #available(iOS 14.0, *) {
VStack{
Profile1Image(imageName:"profileboy")
.padding()
Form{
Section(header: Text("information")){
List(self.httpClient.per ?? [Per](),id: \.id){ person in
VStack {
TextField("name : \(person.name!)", text: $name)
TextField("Instagram : \(person.account!)",text: $instagram)
TextField("pic : \(person.picture!)",text: $head)
TextField("age : \(person.age!)",text: $age)
TextField("birth : \(person.birth!)",text: $birth)
TextField(city : \(person.city!)",text: $live)
TextField("hobby : \(person.hobby!)",text: $habit)
}
}
}.onAppear(perform: {
self.get()
})
}
}.toolbar {
Button("Save"){
self.newPer()
//self.savePer()
}
}
} else {
// Fallback on earlier versions
}
}
}
}
}
struct profile3_Previews: PreviewProvider {
static var previews: some View {
profile3()
}
}
struct Profile1Image: View {
var imageName: String
var body: some View{
Image(uiImage:#imageLiteral(resourceName: "profileboy"))
.resizable()
.frame(width: 250,height: 250)
.clipShape(/*#START_MENU_TOKEN#*/Circle()/*#END_MENU_TOKEN#*/)
}
}

SwiftUI: How to pass a data from one view and use it in the viewModel of another view

Just playing around with passing data between views and using it in view models. Below is the code for the FirstView from which to pass a phone number:
struct FirstView: View {
#State private var phone: String = "9876543210"
#State private var isPresented: Bool = false
var body: some View {
ZStack {
Color.background
VStack {
Spacer()
TextField("type here...", text: $phone)
.onTapGesture {
isPresented.toggle()
}
.fullScreenCover(isPresented: $isPresented, content: {
SecondView(phone: phone)
})
Spacer()
}
}
}
}
How do I use the phone data passed from main view in sub view model as the initial value for the textfield? Below is the unfinished code for SecondView:
struct SecondView: View {
#StateObject private var viewModel = SecondViewModel()
let phone: String // how to assign this obtained data in viewModel.phone?
var body: some View {
ZStack {
Color.background
VStack {
Spacer()
// textfield default text should be the phone number passed from FirstView
TextField("type here...", text: $viewModel.phone)
Spacer()
}
}
}
}
Here's the second view model:
final class SecondViewModel: ObservableObject {
#Published var phone = ""
}
Use init and StateObject(wrappedValue:). Here is the possible solution.
Your view model
final class SubViewModel: ObservableObject {
#Published var phone = ""
init(phone: String) {
self.phone = phone
}
}
Your subview
struct SubView: View {
#StateObject private var viewModel: SubViewModel
private let phone: String
init(phone: String) {
self.phone = phone
_viewModel = StateObject(wrappedValue: SubViewModel(phone: phone))
}
var body: some View {
ZStack {
Color.background
VStack {
Spacer()
// textfield default text should be the phone number passed from main view
TextField("type here...", text: $viewModel.phone)
Spacer()
}
}
}
}

SwiftUI: How to retrieve data from Firestore in second view?

This is my first SwiftUI project and am very new in programming.
I have a View Model and a picker for my Content View, manage to send $selection to Detail View but not sure how to get it to read from the Firestore again. I kinda feel like something is missing, like I need to use an if-else, but can't pinpoint exactly what is the problem. Searched through the forum here but can't seemed to find a solution.
Here is the VM
import Foundation
import Firebase
class FoodViewModel: ObservableObject {
#Published var datas = [Food]()
private var db = Firestore.firestore()
func fetchData(){
db.collection("meals").addSnapshotListener { (snap, err) in
DispatchQueue.main.async {
if err != nil {
print((err?.localizedDescription)!)
return
} else {
for i in snap!.documentChanges{
let id = i.document.documentID
let name = i.document.get("name") as? String ?? ""
let weight = i.document.get("weight") as? Int ?? 0
let temp = i.document.get("temp") as? Int ?? 0
let time = i.document.get("time") as? Int ?? 0
self.datas.append(Food(id: id, name: name, weight: weight, temp: temp, time: time))
}
}
}
}
}
}
struct ContentView: View {
#ObservedObject var foodDatas = FoodViewModel()
#State private var id = ""
#State public var selection: Int = 0
var body: some View {
NavigationView{
let allFood = self.foodDatas.datas
ZStack {
Color("brandBlue")
.edgesIgnoringSafeArea(.all)
VStack {
Picker(selection: $selection, label: Text("Select your food")) {
ForEach(allFood.indices, id:\.self) { index in
Text(allFood[index].name.capitalized).tag(index)
}
}
.onAppear() {
self.foodDatas.fetchData()
}
Spacer()
NavigationLink(
destination: DetailView(selection: self.$selection),
label: {
Text("Let's make this!")
.font(.system(size: 20))
.fontWeight(.bold)
.foregroundColor(Color.black)
.padding(12)
})
}
And after the picker selected a food type, I hope for it to display the rest of details such as cooking time and temperature. Now it is displaying 'index out of range' for the Text part.
import SwiftUI
import Firebase
struct DetailView: View {
#ObservedObject var foodDatas = FoodViewModel()
#Binding var selection: Int
var body: some View {
NavigationView{
let allFood = self.foodDatas.datas
ZStack {
Color("brandBlue")
.edgesIgnoringSafeArea(.all)
VStack {
Text("Cooking Time: \(allFood[selection].time) mins")
}
}
}
}
Appreciate for all the help that I can get.
In your DetailView, you're creating a new instance of FoodViewModel:
#ObservedObject var foodDatas = FoodViewModel()
That new instance has not fetched any data, and thus the index of the selection is out of bounds, because its array is empty.
You could pass your original ContentView's copy of the FoodDataModel as a parameter.
So, the previous line I quoted would become:
#ObservedObject var foodDatas : FoodViewModel
And then your NavigationLink would look like this:
NavigationLink(destination: DetailView(foodDatas: foodDatas, selection: self.$selection) //... etc

SwiftUI: How to Save value from a TextField in core data

I am building a very simple SwiftUI App, following a tutorial.
I created a View that contains a list of items which is #fetched from CoreData, and the list is shown with no problem.
Then I added a modal window with a TextField and a Button to save the data.
This is the code of the modal:
import SwiftUI
struct AddCategoryView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#Environment(\.managedObjectContext) var context
#State public var name: String = ""
#State public var id = UUID()
var body: some View {
VStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Close")
}
Text("Add a new Category")
.font(.largeTitle)
TextField("Enter category name", text: $name)
.padding(.all)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
let category = Category(context: self.context)
category.name = self.name
category.id = self.id
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
}) {
Text("SAVE \(self.name)")
}
}
}
}
struct AddCategoryView_Previews: PreviewProvider {
static var previews: some View {
AddCategoryView()
}
}
In this line,
Text("SAVE \(self.name)")
I am printing (for debugging) the value of the variable name, and I can see that the variables changes to the value that is in the catch statement
self.name = "There is an error!"
So I know that the saving is failing. But I have no idea why.
In the List View I have a button that will open this modal; I changed the value of this Button from
self.showAddModal.toggle()
TO
let category = Category(context: self.context)
category.name = "New Category"
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
And it works! I can see that the List View is updated with the value.
This is the code for the List View
struct CategoriesView: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(entity: Category.entity(), sortDescriptors: []) var categories: FetchedResults<Category>
#State private var showAddModal = false;
#State public var name = ""
var body: some View {
VStack {
Text("\(name)")
List {
ForEach(categories, id: \.id) { category in
VStack {
Text(category.name ?? "Unknown")
}
}
}
Button(action: {
let category = Category(context: self.context)
category.name = "New Category"
do {
try self.context.save()
} catch {
self.name = "There is an error!"
}
}) {
Text("+")
.font(.system(.largeTitle))
.fontWeight(.bold)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.frame(width: 48, height: 48)
.background(Color.purple)
.cornerRadius(24)
.padding(.all)
.shadow(color: Color.black.opacity(0.3),
radius: 3,
x: 3,
y: 3)
}.sheet(isPresented: $showAddModal) {
AddCategoryView()
}
}
}
}
struct CategoriesView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return CategoriesView().environment(\.managedObjectContext, context)
}
}
I have spent the last 2 hours looking and googling for a solution, but I cannot find out what is wrong in my code. I also tried on the simulator but got the same error; I cannot save the core data from the TextField.
Thanks in advance!
Ok I finally found the solution, which maybe is not the right one but at least it works.
.sheet(isPresented: $showAddModal) {
AddCategoryView().environment(\.managedObjectContext, self.managedObjectContext)
}
Yep, just passing the Environment with the context to the Modal View fixed the issue. I am not sure this is the correct way to do it.

Passing filtered #Bindable objects to multiple views in SwiftUI

I’m trying to pass a filter array to multiple views, but the filtering is not working. If I remove the filter, you can pass the array to the next view, but that leads to another error during the ForEach loop. I've posted all the code below.
Does anyone know how you can pass a filter version of a #Bindable array? Also why can't I print sport.name and sport.isFavorite.description in the ForEach loop?
I’m using swiftUI on Xcode 11.0 beta 5.
import SwiftUI
import Combine
struct Sport: Identifiable{
var id = UUID()
var name : String
var isFavorite = false
}
final class SportData: ObservableObject {
#Published var store =
[
Sport(name: "soccer", isFavorite: false),
Sport(name: "tennis", isFavorite: false),
Sport(name: "swimming", isFavorite: true),
Sport(name: "running", isFavorite: true)
]
}
struct Testing: View {
#ObservedObject var sports = SportData()
var body: some View {
VStack {
TestingTwo(sports: $sports.store.filter({$0.isFavorite}))
}
}
}
struct TestingTwo: View {
#Binding var sports : [Sport]
var body: some View {t
NavigationView {
VStack(spacing: 10){
ForEach($sports) { sport in
NavigationLink(destination: TestingThree(sport: sport)){
HStack {
Text(sport.name)
Spacer()
Text(sport.isFavorite.description)
}
.padding(.horizontal)
.frame(width: 200, height: 50)
.background(Color.blue)
}
}
}
}
}
}
struct TestingThree: View {
#Binding var sport : Sport
var body: some View {
VStack {
Text(sport.isFavorite.description)
.onTapGesture {
self.sport.isFavorite.toggle()
}
}
}
}
#if DEBUG
struct Testing_Previews: PreviewProvider {
static var previews: some View {
Testing()
}
}
#endif
Filtering in your case might be better placed in the navigation view, due to your binding requirements.
struct Testing: View {
#ObservedObject var sports = SportData()
var body: some View {
VStack {
TestingTwo(sports: $sports.store)
}
}
}
struct TestingTwo: View {
#Binding var sports : [Sport]
#State var onlyFavorites = false
var body: some View {t
NavigationView {
VStack(spacing: 10){
ForEach($sports) { sport in
if !self.onlyFavorites || sport.value.isFavorite {
NavigationLink(destination: TestingThree(sport: sport)){
HStack {
Text(sport.value.name)
Spacer()
Text(sport.value.isFavorite.description)
}
.padding(.horizontal)
.frame(width: 200, height: 50)
.background(Color.blue)
}
}
}
}
}
}
}
Now you can switch the isFavorite state either within the action implementation of a button, or while specifying the integration of you TestingTwo view.
struct Testing: View {
#ObservedObject var sports = SportData()
var body: some View {
VStack {
TestingTwo(sports: $sports.store, onlyFavorites: true)
}
}
}
Regarding the second part of your question: Note the value addendum in the ForEach loop. You're dealing with as binding here (as ForEach($sports) indicates), hence sport is not an instance of Sport.
You can't get a #Binding from a computed property, since the computed property is computed dynamically. A typical way to avoid this is to pass in ids of the sports objects and the data store itself, whereby you can access the sports items via id from the store.
If you really want to pass a #Binding in you have to remove the filter (pass in an actually backed array) and modfy the ForEach like the following:
ForEach($sports.store) { (sport: Binding<Sport>) in