struct NotesView: View {
#State var notesArray = [Note]()
public var deleteid: String
var body: some View {
List{
ForEach(notesArray, id: \._id) { notesArray in
NavigationLink(destination: AddNotesView(addNotesViewIdentifier: "updateNote", id: notesArray._id, title: notesArray.title, note: notesArray.note, noteDate: notesArray.date)){
HStack {
Text(notesArray.title)
deleteid = notesArray._id //ERROR - Cannot assign to property: 'self' is immutable
}
}
}
.onDelete(perform: deleteNoteAtIndex)
}
}
func deleteNoteAtIndex(at offsets: IndexSet){ APIFunctions.functions.DeleteNote(id: _id) }
I was expecting the variable "deleteid" to update.
I assumed you can modify any variable by calling that variable and set it equal to a new value.
Like this
First declare variable:
var deleteid: String
next modify variables string valve
deleteid = notesArray._id
A couple of things:
This isn't directly related to your question, but may help you navigation your own code better... When you create a ForEach view to iterate over an array, you should use a different name for the value that represents each element in the iteration. Here, you're using the name notesArray for your array of notes, then creating a second local variable called notesArray for the loop. That variable inside the block will be an instance of Note, so I'd name it note, e.g.:
ForEach(notesArray, id: \._id) { note in
NavigationLink(destination: AddNotesView(addNotesViewIdentifier: note._id, // etc
}
If you want variables to be modifiable inside views, they should be #State variables. This is important due to the way Swift struct lifecycles work, and how the SwiftUI rendering system loads and reloads structs as it works out what has changed.
I'm not entirely sure what deleteid is supposed to represent here, and it's possible you don't need it at all. If you're using the onDelete modifier to implement SwiftUI's native swipe-to-delete system, SwiftUI will give you an IndexSet, which is a collection (usually of just one) of the positions of the item(s) to delete in the array.
From there, you can find the item(s) at each index and then either remove them, or lookup some other value (e.g., their _id attribute) and do some other operation on them.
So the method you might call in onDelete could look something like:
func deleteNoteAtIndex(offsets: IndexSet) {
// get the array objects that the offsets point to
let notes = offsets.map { noteArray[$0] }
for note in notes {
APIFunctions.functions.deleteNote(id: note._id)
}
}
Related
I'm following a Kodeco tutorial(https://www.kodeco.com/4161005-mvvm-with-combine-tutorial-for-ios) for SwiftUI and Combine which hits an API and shows the data in a list. The tutorial doesn't explain how I can navigate to a detail view from the list, and I would like to implement this, but I'm having trouble adapting my code to do this.
My List is set up like this:
var body: some View {
NavigationView {
List {
characterSection
}
.navigationTitle("Characters")
}
}
Character Section:
var characterSection: some View {
Section {
ForEach(viewModel.dataSource, content: CharacterListRowView.init(viewModel:))
}
}
I would like to be able to do something like the following to navigation to a detail screen, but I'm getting errors:
ForEach(viewModel.dataSource, content: CharacterListRowView.init(viewModel:)) { item in
NavigationLink(destination: viewModel.characterDetailView) {
}
}
This obviously does not work but hopefully you can see what I am trying to do. I'm receiving these errors:
Generic parameter 'ID' could not be inferred,
Incorrect argument label in call (have ':content::', expected '_:id:content:')
Trailing closure passed to parameter of type 'KeyPath<Array.Element, ID>' that does not accept a closure
Here is my row view model in case the issue is with this, could be something to do with the ID?:
import Foundation
import SwiftUI
struct CharacterRowViewModel: Identifiable {
private let item: CharacterListResponse
var id: Int {
return item.char_id
}
var title: String {
guard let title = item.name?.description else { return "" }
return title
}
init(item: CharacterListResponse) {
self.item = item
}
}
Thanks for any help, I am new to SwiftUI.
One of your biggest problems is that your ForEach view is defining its contents twice, and the compiler (quite rightly) doesn't understand.
In the form you're using, ForEach takes two arguments:
the items to loop through
what view to generate for each item in the loop
That second argument is called content and it expects to be given something that receives a single item, and returns the View to use for that item.
When you use the form
ForEach(viewModel.dataSource, content: CharacterListRowView.init(viewModel:))
you're passing in a function – in this case CharacterListRowView.init - that receives a row view model, and returns a CharacterListRowView.
That's convenient when the init method matches the expected function type. But the more common form is the type you're trying to switch to - a block that receives an item, and inside which you build the view for the item. When we use that form, we usually use Swift's trailing block syntax. This allows us to omit the argument name. So when we say
ForEach(items) { item in
// view code here
}
it's the same as
ForEach(items, content: { item in
// view code here
})
Now, look again at the code you've amended:
ForEach(viewModel.dataSource, content: CharacterListRowView.init(viewModel:)) { item in
NavigationLink(destination: viewModel.characterDetailView) {
}
}
Here, you're not replacing the first form of content: with a block form, you're attempting to provide a second content block. The compiler is having a meltdown because you're providing two items to something that only expects one. When this happens, sometimes the compiler's error messages can get very confusing -- it reports how what it's tried to do to make your code work hasn't worked, rather than the direct cause of the failure.
So in your ForEach loop, remove the explicit content::
ForEach(viewModel.dataSource) { item in
NavigationLink(destination: viewModel.characterDetailView) {
}
}
You may still have problems after this step, but hopefully any error messages will be a little bit easier to understand.
I have an ObservableObject with a published dictionary of strings to arrays with arrays with Ints:
class MyObservableObject: ObservableObject {
#Published var myDict: [String: [[Int]]]
}
And I want to pass one array of Ints as a Binding from inside the same class to a function of a different struct:
{
...
func someFunc(key: String, index: Int) {
someStruct.func(myDict[key]![index])
}
...
}
I understand that #Published vars can't be passed as Bindings. I'm still hoping that there's any way to achieve this. I also tried storing a reference to the array in the other struct using an inout variable, also without success.
#Published vars can't be passed as Bindings
It is not true - it is possible, via projected value using '$', but you want to pass not a property, but part of value of a property, and this is different thing.
The context is not clear and this someFunc smells not-well :) - I'd say it is needed some refactoring here, but, anyway, technically it is possible to do what you want using dynamically in-place generated binding, like
func someFunc(key: String, index: Int) {
guard myDict[key] != nil else { return }
someStruct.func(Binding<[Int]>(
get: { self.myDict[key]![index] },
set: { self.myDict[key]![index] = $0 }
))
}
This gives me the following error:
Cannot force unwrap value of non-optional type 'Binding<Person?>
I don't understand why I can't force unwrap a binding?
struct Person {
var name: String
}
struct ContentView: View {
#State private var person: Person? = Person(name: "Peter")
var body: some View {
if person != nil {
TextField("", text: $person!.name)
}
}
}
When you declare a State variable in your view, you'll get a Binding to the value by adding a leading $ to the variable's name.
So, in your code, you'll have a $person that is binding to an optional Person type. $person is Binding<Person?>
To pass value to TextField you'll need a Binding<String>. you can't force-unwrap $person because it's not an optional value. It's a Binding to an optional type. To access the name field inside the Person struct, you'll need a Binding<Person> instead.
Fortunately, there's a method to get what you want.
By using this initializer, you'll have a Binding<Person>?. Note that now instead of a Binding to an optional, you have an optional Binding.
You should be able to use this new binding like this:
// Binding($person) returns Binding<Person>?
TextField("", text: Binding($person)!.name)
Update:
As #Jessy mentioned in the comments, instead of force-unwrapping the optional binding, we can use map to transform the returned Binding to a TextField
var body: some View {
Binding($person).map {
TextField("", text: $0.name)
}
}
The bolded line (ie var text: String...) gives a "Cannot use instance member 'numberOfDevice' within property initializer; property initializers run before 'self' is available" error. Do I need an init? If so where? Is there a different solution?
struct PairView: View {
var theClass = BluetoothManager()
init() {theClass.viewDidLoad()}
var body: some View {
List {
ForEach(0..<BluetoothManager.peripheralArray.count) { number in //iterates thru 0 to array's count
ConnectionView(numberOfDevice: number) // create a ConnectionView for each number
}
}
}
}
//-------
struct ConnectionView: View {
var numberOfDevice: Int
**var text: String = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!**
// 'name' is a String property of the B.M. class's array's 'numberOfDevice index.'
var body: some View {
ZStack{
RoundedRectangle(cornerRadius: 10.0).fill(Color.blue)
Text(text).foregroundColor(Color.black)
}
}
}
You can use read-only computed property with short-hand.
var text: String {
return (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
}
The error you encountered means you can't use the numberOfDevice variable to instantiate another variable. However, you can use the number you pass to your init method.
Try the following:
struct ConnectionView: View {
var numberOfDevice: Int
var text: String
init(numberOfDevice: Int) {
self.numberOfDevice = numberOfDevice
self.text = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
}
...
}
Note: I don't recommend force-unwrapping (!). If possible try to provide a default value.
Also, BluetoothManager looks like a type and not like an instance of a class. Make sure you access the peripheralArray property on the valid object and not on the BluetoothManager type.
You can use lazy keyword for that:
lazy var text: String = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
What is lazy?
lazy means that it will postpone initialization until someone calls the variable and it will not possible if self is not initialized. So you will be sure self is ready before accessing that value.
Why?
When you call numberOfDevice, you are actually calling self.numberOfDevice, but swift is smart enough to let you not explicitly write self keyword.
The issue here is that self is not initialized yet when you are assigning a value to a variable.
So you need to make sure the variable is initialized BEFORE accessing self.
My apologies if this is not the right question to ask, as I am completely new to SwiftUI and iOS programming in general. The question indicates what I want to do, and the error I'm getting I believe is a red herring because of the SwiftUI compiler. It's likely that I am taking the incorrect approach to solving this problem altogether.
I am using XCode Version 11.2.1 (11B500)
View utilizing the ObservedObject:
struct Results: View {
var jobId: String
#ObservedObject var jobDetailService: JobDetailService
init(jobId: String) {
self.jobId = jobId
jobDetailService = JobDetailService(jobId: jobId)
}
var body: some View {
//... view code here
}
}
And it is within this view that I am getting the error (at the ZStack line) "Generic parameter 'C0' could not be inferred". When I comment out the NavigationLink block, the error goes away. Further, when the Results view does not depend on the jobId parameter (and we construct JobDetailService inline with #ObservedObject var jobDetailService = JobDetailService(), this all works. However, I need to be able to pass the jobId parameter to the JobDetailService in order to make the network call to fetch and publish the data.
struct JobList: View {
#ObservedObject var jobListService = JobListService()
var body: some View {
NavigationView {
List(jobListService.jobs) {job in
ZStack {
JobCard(name: job.fullName, date: job.lastUpdated)
NavigationLink(destination: Results(jobId: job.jobId)) {
EmptyView()
}
}
}
}
}
}
After reading this article, and thinking about Asperi's advice on not solely relying on initialization, I opted to do the following:
Remove custom initializer from JobDetailService and instead instantiate the service inside my Results view. Then in an .onAppear method on the Results view, call the getJobDetail method from JobDetailService which in turn makes the network call that populates the #ObservedObject. This allows me to pass in the parameters I need and control when the network call is made. Maybe not the right pattern for this problem but it works for my use case for now.
I assume the following should help you:
Declaration...
struct Results: View {
#ObservedObject var jobDetailService: JobDetailService
var body: some View {
//... view code here
}
}
... and usage
NavigationLink(destination: Results(jobDetailService: JobDetailService(jobId: jobId))) {
EmptyView()
}