How to update variables in ContentView with SheetView function call? - arrays

I have a code with a sheet view and a normal view. When I press a button within my sheet view I make an API call. This API call then updates some variables which I'm trying to display in my regular view using ´ForEach´. However, when I make the call in the sheet view and close it down, the array does not seem to update in my normal view. My view just remains blank (except for displaying the button that says "Show sheet". How do I make the array update so that it isn't blank?
Here is my regular view:
// MARK: - Schedule View
struct ScheduleView: View {
#State var selectedTab = 0
#State private var showingSheet = true
var body: some View {
GeometryReader { geo in
VStack {
ForEach(SheetView().vm.Trips, id: \.self) { dict in
Text(dict["Origin"]!) // I want this varible to update, but I doesn't
Text(dict["Destination"]!) // It instead remains blank
}
Button("Show sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
.frame(width: geo.size.width*0.7, height: geo.size.height*0.06)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(11)
.position(x: geo.size.width/2, y: geo.size.height/2)
// MARK: - Padding funkar inte
}
}.padding()
}
}
And here is my sheet view:
struct SheetView: View {
#Environment(\.presentationMode) var presentationMode
#StateObject var vm: PlanTripViewModel = PlanTripViewModel()
#State var selected = 0
var body: some View {
GeometryReader {geo in
ZStack{
VStack {
TextField("From", text: $vm.origin.input).padding()
TextField("To", text: $vm.dest.input).padding()
TextField("00:00", text: $vm.arrivalTime).padding()
TextField("yyyy-mm-dd", text: $vm.travelDate).padding()
Button("Add trip") {
vm.fetchStatus = .start // This starts the API call in another file of mine
presentationMode.wrappedValue.dismiss() // This closes the sheet view
}.padding()
}.foregroundColor(.blue)
}
}
}
}

Right now, you're making a new SheetView instance on every single ForEach call -- it's not the same one that you're using in your sheet call.
To solve this, you'll want to store the state in your parent view and give the sheet view a reference to it.
struct SheetView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var vm: PlanTripViewModel //<-- Here
#State var selected = 0
var body: some View {
GeometryReader {geo in
ZStack{
VStack {
TextField("From", text: $vm.origin.input).padding()
TextField("To", text: $vm.dest.input).padding()
TextField("00:00", text: $vm.arrivalTime).padding()
TextField("yyyy-mm-dd", text: $vm.travelDate).padding()
Button("Add trip") {
vm.fetchStatus = .start // This starts the API call in another file of mine
presentationMode.wrappedValue.dismiss() // This closes the sheet view
}.padding()
}.foregroundColor(.blue)
}
}
}
}
struct ScheduleView: View {
#State var selectedTab = 0
#State private var showingSheet = true
#StateObject var vm: PlanTripViewModel //<-- Here
var body: some View {
GeometryReader { geo in
VStack {
ForEach(vm.Trips, id: \.self) { dict in
Text(dict["Origin"]!)
Text(dict["Destination"]!)
}
Button("Show sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView(vm: vm) //<-- Here
}
.frame(width: geo.size.width*0.7, height: geo.size.height*0.06)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(11)
.position(x: geo.size.width/2, y: geo.size.height/2)
// MARK: - Padding funkar inte
}
}.padding()
}
}
(Note: you may know this already, but force unwrapping your dictionary values with ! will cause a crash if the keys don't exist. You may want to use optional binding (if let) or another safe check to make sure they exist.)

Related

Update Textfield dynamically in swiftui

In the following screen, there is a TextField which can be edited and can be updated after receiving value from API
struct VerifyCard: View {
#State var cartSample : CartSampleDetail
#EnvironmentObject var bookMod : BookTestModel
var body: some View {
VStack(alignment:.leading){
HStack{
TextField("enter Barcode", text: Binding(get: {cartSample.Barcode ?? ""}, set: {cartSample.Barcode = $0}))
.textFieldStyle(RoundedBorderTextFieldStyle())
.environmentObject(bookMod)
Button(action: {}, label: {
Image(systemName: "barcode.viewfinder")
})
}
HStack{
GenerateBarcode(bookMod: _bookMod, cartBar:$cartSample, showBar: false)
}
}
}.padding()
}}}
whenever, I am clicking generate barcode in the alert button it is calling the API, response received and barcode too but the textfield is not showing the barcode. I tried DispatchQueue also still same result. Ideally, when button is clicked the textfield should also be filled with the barcode. In adition, I tried to updated the textfield using a published var in the generateBarcode() than it successfully updating the textfield but than it is filling all textfields of the list but it should only fill the coressponding textfield of which button is clicked. Cannot able to sort out this.
This is the GenerateBarcode screen
#EnvironmentObject var bookMod : BookTestModel
#Binding var cartBar : CartSampleDetail
#State var showBar = Bool()
var body: some View {
HStack{
// Text(barcode)
Button(action: {
self.showBar.toggle()
}, label: {
Text("Generate Barcode")
.padding(8)
.foregroundColor(.black)
.background(RoundedRectangle(cornerRadius: 6,style: .continuous).fill(Color.yellow))
}).alert("Barcode Generate", isPresented: $showBar, actions: {
Button("Cancel",role:.cancel ,action: {})
Button(action: {
bookMod.generateBarcode()
// DispatchQueue.main.asyncAfter(deadline:.now()){
// if bookMod.genBarcode.Success{
cartBar.Barcode = bookMod.genBarcode.Value
// }
// }
}, label: {
Text("Generate Barcode")
})
},message: {
Text("If you do not have barcode you can generate it here”)
})
The VerifyCard is used in the list as below:
struct VerifySample: View {
#ObservedObject var bookMod : BookTestModel
#ObservedObject var vial : VialImageModel
var body: some View {
VStack{
List(bookMod.cartSample,id:\.self){sample in
VerifyCard(cartSample: sample, vial: vial.link)
.environmentObject(BookTestModel())
}
}.onAppear{
Task{
try await bookMod.getSampleDetails()
try await vial.getVialLink()
}
}
The above is producing a list of samples represented by VerifyCard struct as explained above.
The following is the CartSampleDetail struct
struct CartSampleData:Codable{
let Message : String
let CartSample : [CartSampleDetail]
enum CodingKeys:String, CodingKey{
case Message
case CartSample = "ResponseData"
}}
struct CartSampleDetail:Codable,Hashable{
var SampleName : String
var PackageId : Int
var TestSampleTypeId : String
var Barcode : String?
var SampleBarcodeDigit : Int
var ColourCode : String
}
I am using this struct to receive API response and also trying to update its Barcode field using the above GenerateBarcode button.
How to display the barcode/text received from generateBarcode() in the textfield after clicking the button.

How to use array of data from different Views - Swift

I have application that has 2 Views. One just keeps an empty array and shows it, and the other one view should add data in this array. In the end I want added info to show on the view that keeps this array.
I've already tried to create an variable of another view inside of the first one, and try to change array, but as i experienced, it doesn't work, because it just created a new instance of view, that has nothing in common with the one that I want to update.
`
//View that keeps array "savedArticles"
struct SavedNewsView: View {
#State var savedArticles: [Article] = []
func removeArticle(at offsets:IndexSet) {
savedArticles.remove(atOffsets: offsets)
}
var body: some View {
NavigationView {
List {
ForEach(savedArticles, id: \.self) {article in
NewsComponentView(title: article.title, description: article.description, urlToImage: article.urlToImage, url: article.url)
}
.onDelete(perform: removeArticle)
}
.listStyle(.plain)
}
.navigationTitle("Saved News")
}
}
`
And this is the view that updates this array:
`
struct NewsView: View {
#StateObject var viewModel = ViewModel()
#State var savedArray = SavedNewsView()
var body: some View {
List {
ForEach(viewModel.news, id: \.self) {article in
if article.urlToImage != nil {
NewsComponentView(title: article.title, description: article.description, urlToImage: article.urlToImage, url: article.url)
.swipeActions() {
Button {
savedArray.savedArticles.append(Article(title: article.title, description: article.description, url: article.url, urlToImage: article.urlToImage))
} label: {
Label("Save", systemImage: "bookmark")
}
.tint(.yellow)
}
}
}
}
.listStyle(.plain)
.onAppear {
viewModel.fetch()
}
}
`
You can pass write access to an #State to a subview using #Binding, e.g.
struct NewsView: View {
#Binding var savedArticles: [Article]
NewsView(savedArticles: $savedArticles)

How to bind a list of textfields that edit a variable within an a core data array?

Swift ui requires a Binding<String> to link to the value you are updating in a text field. Much like the native iPhone Reminders app, I am looking to permit inline editing a list that will persist.
The attached code works only but gives the same name for each item due to them all being bound to the same variable. How can I bind this to the [FruitEntity] array?
class CoreDataViewModel: ObservableObject {
//static let instance = CoreDataViewModel()
let container: NSPersistentContainer
let context: NSManagedObjectContext
#Published var savedEntities: [FruitEntity] = []
}
struct Screen: View {
#StateObject var vm = CoreDataViewModel()
var body: some View {
List{
ForEach(vm.savedEntities, id: \.self) {entity in
VStack{
HStack {
TextField("\(entity.name ?? "Workout Name...")", text: $questionVariable)
.onChange(of: entity.name) { text in
entity.name = questionVariable
}
}
.onDelete(perform: vm.deleteFruit)
.onMove(perform: moveItem)
}
}
}
}
}
You can just move the TextField to a separate view, with its own #State var for the field and another var for the entity.
Create a view like the following one:
struct ChangeName: View {
// Will change the entity
let entity: FruitEntity
// Will update the field
#State private var questionVariable = ""
var body: some View {
TextField("\(entity.name ?? "Workout Name...")", text: $questionVariable)
.onChange(of: questionVariable) { text in
entity.name = text
// Remember to save the persistent container/ managed-object-context
}
}
}
Call it in your main view:
struct Screen: View {
List{
ForEach(vm.savedEntities, id: \.self) {entity in
VStack{
HStack {
ChangeName(entity: entity)
}
}
.onDelete(perform: vm.deleteFruit)
.onMove(perform: moveItem)
}
}
}

How do you bind a list view

I have a CoordinateList view that shows a list of CoordinateRow views that have been entered, and are editable in place (in the list view). To add a new point to the list, the user presses a button at the bottom of the list, and it adds a row (without going to another screen). How do I make it update the view to show this new entry? I have tried wrapping the append function of the list of coordinates with a function so that I can call objectWillChange.send() when adding to the list, but it doesn't seem to do anything.
I guess I don't have enough reputation to upload an image, but here's an image:
import SwiftUI
class LocationTime : ObservableObject {
#Published var lat: String = "0.0"
#Published var lon: String = "0.0"
#Published var timestamp: String = "SomeDateTime"
}
class ModelData: ObservableObject {
#Published var positionCoords = [LocationTime]()
func appendPosition(_ loc: LocationTime) {
objectWillChange.send()
positionCoords.append(loc)
}
}
struct CoordinateRow: View {
#EnvironmentObject var modelData: ModelData
var pointIndex : Int
var body: some View {
HStack {
Text("Lon: ")
TextField("40",text:$modelData.positionCoords[pointIndex].lon)
Text("Lat: ")
TextField("",text:$modelData.positionCoords[pointIndex].lat)
Text("Time: ")
TextField("time",text:$modelData.positionCoords[pointIndex].timestamp)
}.padding()
.overlay(RoundedRectangle(cornerRadius:16)
.stroke(Color.blue,lineWidth:4.0))
}
}
struct CoordinateList: View {
#EnvironmentObject var modelData : ModelData
var body: some View {
VStack{
Text("Location Log")
.font(.largeTitle).padding()
List{
ForEach(modelData.positionCoords.indices){
CoordinateRow(pointIndex: $0).environmentObject(modelData)
}
}
Button(action:{
modelData.appendPosition(LocationTime())
print(modelData.positionCoords.count)
}){
Image(systemName: "plus.circle")
.imageScale(.large)
.scaleEffect(2.0)
.padding()
}
Spacer()
}
}
}
You need identify records in ForEach
ForEach(modelData.positionCoords.indices, id: \.self){ // << here !!
CoordinateRow(pointIndex: $0).environmentObject(modelData)
}
and by the way, remove objectWillChange.send()
func appendPosition(_ loc: LocationTime) {
// objectWillChange.send() // << called automatically for #Published
positionCoords.append(loc)
}

SwuiftUI check mark single row

on my project in SwiftUi I'm listing some data in a form, I want to give the user the ability to select a single item in the list with the tap action( can not select more than one in the list)
on the code below I have create a list:
List {
ForEach(listOfCycle, id: \.self) {db in
dbList(db: db, ciclo: self.$cycleSelected)
}
}
and for each row I have the view dbList
import SwiftUI
struct dbList: View {
#State var db : Cycle
#Binding var ciclo : Cycle?
#State var cicloSelected : Bool = false
var body: some View {
HStack{
Text("Database:")
Spacer()
Text(db.idDatabaseAirports ?? "").foregroundColor(self.cicloSelected ? .green: .black).font(self.cicloSelected ? .title : .body)
if self.cicloSelected {
Image(systemName: "checkmark.circle")
}
}.onTapGesture {
self.cicloSelected.toggle()
self.ciclo = self.db
}
}
}
the logic work but the user can tap on multiple row and select more than one, on my project I have to put the checkmark only on one row at the time can't be more than one.
is there any way I can put to avoid multiple selection.
Thanks a lot
The provided code is not testable, so only idea (scratchy, but should be clear).
Note: make sure that Cycle is Equatable
struct dbList: View {
#State var db : Cycle
#Binding var ciclo : Cycle?
var body: some View {
HStack{
Text("Database:")
Spacer()
Text(db.idDatabaseAirports ?? "").foregroundColor(self.db == self.cyclo ? .green: .black).font(self.cicloSelected ? .title : .body)
if self.db == self.cyclo {
Image(systemName: "checkmark.circle")
}
}.onTapGesture {
self.ciclo = self.db
}
}
}

Resources