Binding an Array through multiple Views in SwiftUI - ios

I'm stuck with this problem. I've the following struct that builds a List from a #Binding Array
struct AppleList: View {
#Binding var apples: [Apple]
var body: some View {
NavigationView {
List {
ForEach(apples) { apple in
NavigationLink(destination: AppleDetail(apple: $apple)) {
AppleRow(apple: apple)
}
}
}
}
}
}
AppleDetail has an edit button that switches between AppleSummary and AppleEditor, so here the apple will be modified.
struct AppleDetail: View {
#Binding var apple: Apple
var body: some View {
...
}
}
AppleRow doesn't need to modify apple.
struct AppleRow: View {
var apple: Apple
var body: some View {
...
}
}
The problem is in the ForEach loop: how can make a binding element from a binding array of elements that when they will be modified will send the modification to the parent of AppleList?

Solution was quite simple:
ForEach(apples.indices) { idx in
NavigationLink(destination: AppleDetail(transaction: self.$apples[idx])) {
AppleRow(transaction: apples[idx])
}
}

Related

Infinite loop by using #Binding when passing data between views

High-level description:
There is a nested view problem when a state object is being passed through views. At the end of the deepest view in the hierarchy, the app is frozen and memory consumption is increasing continuously.
Use-case
Partners list → Partner detail → (Locations list) → Location detail
Code-snippets
class PartnerViewModel: ObservableObject {
#Published var partners: [Partner] = Partner.partners
}
This view is loaded into a TabView and a NavigationStack components in the parent class.
struct PartnerListView: View {
#StateObject var viewModel = PartnerViewModel()
var body: some View {
List($viewModel.partners, id: \.self) { $partner in
NavigationLink {
PartnerDetailView(partner: $partner)
} label: {
Text(partner.name)
}
}
}
}
struct PartnerDetailView: View {
#Binding var partner: Partner
var body: some View {
Form {
Section("Locations") {
List($partner.locations, id: \.self) { $location in
NavigationLink {
LocationDetailView(location: $location)
} label: {
Text(location.name)
}
}
}
}
}
}
struct LocationDetailView: View {
#Binding var location: Location
var body: some View {
TextField("Name", text: $location.name)
}
}
The following snippets are workaround and it works but it might be temporary because I don't understand why the first attempt doesn't work and why this one does. I haven't found any resources that could give an example of this scenario.
struct PartnerDetailView: View {
#Binding var partner: Partner
var body: some View {
Form {
Section("Locations") {
List($partner.locations, id: \.self) { $location in
NavigationLink {
LocationDetailView(partner: $partner, locationIndex: partner.locations.firstIndex(of: location) ?? 0)
} label: {
Text(location.name)
}
}
}
}
}
}
struct LocationDetailView: View {
#Binding var partner: Partner
var locationIndex: Int
var body: some View {
TextField("Name", text: $partner.locations[locationIndex].name)
}
}
Is it possible that I am not passing values between views properly?🤔

SwiftUI - "Argument passed to call that takes no arguments"?

I have an issue with the coding for my app, where I want to be able to scan a QR and bring it to the next page through navigation link. Right now I am able to scan a QR code and get a link but that is not a necessary function for me. Below I attached my code and got the issue "Argument passed to call that takes no arguments", any advice or help would be appreciated :)
struct QRCodeScannerExampleView: View {
#State private var isPresentingScanner = false
#State private var scannedCode: String?
var body: some View {
VStack(spacing: 10) {
if let code = scannedCode {
//error below
NavigationLink("Next page", destination: PageThree(scannedCode: code), isActive: .constant(true)).hidden()
}
Button("Scan Code") {
isPresentingScanner = true
}
Text("Scan a QR code to begin")
}
.sheet(isPresented: $isPresentingScanner) {
CodeScannerView(codeTypes: [.qr]) { response in
if case let .success(result) = response {
scannedCode = result.string
isPresentingScanner = false
}
}
}
}
}
Page Three Code
import SwiftUI
struct PageThree: View {
var body: some View {
Text("Hello, World!")
}
}
struct PageThree_Previews: PreviewProvider {
static var previews: some View {
PageThree()
}
}
You forgot property:
struct PageThree: View {
var scannedCode: String = "" // << here !!
var body: some View {
Text("Code: " + scannedCode)
}
}
You create your PageThree View in two ways, One with scannedCode as a parameter, one with no params.
PageThree(scannedCode: code)
PageThree()
Meanwhile, you defined your view with no initialize parameters
struct PageThree: View {
var body: some View {
Text("Hello, World!")
}
}
For your current definition, you only can use PageThree() to create your view. If you want to pass value while initializing, change your view implementation and consistently using one kind of initializing method.
struct PageThree: View {
var scannedCode: String
var body: some View {
Text(scannedCode)
}
}
or
struct PageThree: View {
private var scannedCode: String
init(code: String) {
scannedCode = code
}
var body: some View {
Text(scannedCode)
}
}
This is basic OOP, consider to learn it well before jump-in to development.
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

Cannot convert value of type 'Binding<_>' to expected argument type 'Binding<Card>'

I am trying to create a binding to a FetchedResults item, error is on $items[i]:
struct NavView: View {
#Binding var item : Card
...
}
struct ContentView: View {
private var items: FetchedResults<Card>
var body: some View {
List {
ForEach(items.indices, id:\.self) { i in
NavigationLink {
NavView(item: $items[i])
}
}
}
}
}
Changing the Binding to ObservedObject compiles and seems to work properly, although I feel like I'm violating single source of truth policy by creating a new ObservedObject.
struct NavView: View {
#ObservedObject var item : Card
...
}
struct ContentView: View {
private var items: FetchedResults<Card>
var body: some View {
List {
ForEach(items) { item in
NavigationLink {
NavView(item: item)
}
}
}
}
}

Is there a way to create objects in swiftUI view based on a value gathered from a previous view?

I have recently started my journey into iOS development learning swift and swift UI. I keep running into issues when it comes to app architecture. The problem i am trying to solve is this: Let's say I have an app where the user first selects a number and then presses next. The user selected number is supposed to represent the number of text fields that appear on the next view. For example, if the user selects 3 then 3 text fields will appear on the next view but if the user selects 5 then 5 texts fields will appear. Is the solution to just have a view for each case? Or is there some way to dynamically add objects to a view based on the user input. Can anyone explain how they would handle a case like this?
Views can get passed parameters (including in NavigationLink) that can determine what they look like. Here's a simple example with what you described:
struct ContentView : View {
#State var numberOfFields = 3
var body: some View {
NavigationView {
VStack {
Stepper(value: $numberOfFields, in: 1...5) {
Text("Number of fields: \(numberOfFields)")
}
NavigationLink(destination: DetailView(numberOfFields: numberOfFields)) {
Text("Navigate")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct DetailView : View {
var numberOfFields : Int
var body: some View {
VStack {
ForEach(0..<numberOfFields) { index in
TextField("", text: .constant("Field \(index + 1)"))
}
}
}
}
Notice how numberOfFields is stored as #State in the parent view and then passed to the child view dynamically.
In general, it would probably be a good idea to visit some SwiftUI tutorials as this type of thing will be covered by most of them. Apple's official tutorials are here: https://developer.apple.com/tutorials/swiftui
Another very popular resource is Hacking With Swift: https://www.hackingwithswift.com/100/swiftui
Update, based on comments:
struct ContentView : View {
#State var numberOfFields = 3
var body: some View {
NavigationView {
VStack {
Stepper(value: $numberOfFields, in: 1...5) {
Text("Number of fields: \(numberOfFields)")
}
NavigationLink(destination: DetailView(textInputs: Array(repeating: "test", count: numberOfFields))) {
Text("Navigate")
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct Model : Identifiable {
var id = UUID()
var text : String
}
class ViewModel : ObservableObject {
#Published var strings : [Model] = []
}
struct DetailView : View {
var textInputs : [String]
#StateObject private var viewModel = ViewModel()
var body: some View {
VStack {
ForEach(Array(viewModel.strings.enumerated()), id: \.1.id) { (index,text) in
TextField("", text: $viewModel.strings[index].text)
}
}.onAppear {
viewModel.strings = textInputs.map { Model(text: $0) }
}
}
}

How to notify view that the variable state has been updated from a extracted subview in SwiftUI

I have a view that contain users UsersContentView in this view there is a button which is extracted as a subview: RequestSearchButton(), and under the button there is a Text view which display the result if the user did request to search or no, and it is also extracted as a subview ResultSearchQuery().
struct UsersContentView: View {
var body: some View {
ZStack {
VStack {
RequestSearchButton()
ResultSearchQuery(didUserRequestSearchOrNo: .constant("YES"))
}
}
}
}
struct RequestSearchButton: View {
var body: some View {
Button(action: {
}) {
Text("User requested search")
}
}
}
struct ResultSearchQuery: View {
#Binding var didUserRequestSearchOrNo: String
var body: some View {
Text("Did user request search: \(didUserRequestSearchOrNo)")
}
}
How can I update the #Binding var didUserRequestSearchOrNo: String inside the ResultSearchQuery() When the button RequestSearchButton() is clicked. Its so confusing!
You need to track the State of a variable (which is indicating if a search is active or not) in your parent view, or your ViewModel if you want to extract the Variables. Then you can refer to this variable in enclosed child views like the Search Button or Search Query Results.
In this case a would prefer a Boolean value for the tracking because it's easy to handle and clear in meaning.
struct UsersContentView: View {
#State var requestedSearch = false
var body: some View {
ZStack {
VStack {
RequestSearchButton(requestedSearch: $requestedSearch)
ResultSearchQuery(requestedSearch: $requestedSearch)
}
}
}
}
struct RequestSearchButton: View {
#Binding var requestedSearch: Bool
var body: some View {
Button(action: {
requestedSearch.toggle()
}) {
Text("User requested search")
}
}
}
struct ResultSearchQuery: View {
#Binding var requestedSearch: Bool
var body: some View {
Text("Did user request search: \(requestedSearch.description)")
}
}
Actually I couldn't understand why you used two struct which are connected to eachother, you can do it in one struct and Control with a state var
struct ContentView: View {
var body: some View {
VStack {
RequestSearchButton()
}
}
}
struct RequestSearchButton: View {
#State private var clicked : Bool = false
var body: some View {
Button(action: {
clicked = true
}) {
Text("User requested search")
}
Text("Did user request search: \(clicked == true ? "YES" : "NO")")
}
}
if this is not what you are looking for, could you make a detailed explain.

Resources