I am building a List based on my elements in an array I fetched before.
I am fetching all the entities.. when the user makes a search in the search bar, I want to filter my List. I am NOT doing a new FetchRequest, I just want to filter my objects.
That is the code I am using at the moment:
List(selection: $selectedDocument)
{
ForEach(self.documentItems, id: \.self) { document in
HStack(spacing: 0)
{
if (self.checkSearchString(document: document))
{
ListRow(document: document).tag(document)
}
}
I am having a List, then my ForEach loop. In that loop, I want to decide if I show that element or not. The problem is, that even if I do not want to show the element, there is still a small view inside my List. I know why, it is because I still render that HStack().
I basically need to drag that HStack() inside my If, however that is not working for me. I think it is because I need to render a view inside my List. But how can I contiuue my ForEach without rendering something.
That is what I want to achieve, BUT it is not working:
List(selection: $selectedDocument)
{
ForEach(self.documentItems, id: \.self) { document in
if (self.checkSearchString(document: document))
{
HStack(spacing: 0)
{
ListRow(document: document).tag(document)
}
}
Thanks in advance!
filter your data BEFORE passing it to ForEach constuctor.
ForEach(self.documentItems.filter {self.checkSearchString(document: $0)}, id: \.self) { document in
HStack(spacing: 0)
{
ListRow(document: document).tag(document)
}
}
You need to use Group to wrap different views provided by condition, like below
ForEach(self.documentItems, id: \.self) { document in
Group {
if (self.checkSearchString(document: document))
{
HStack(spacing: 0)
{
ListRow(document: document).tag(document)
}
}
else
{
EmptyView()
}
}
}
List(selection: $selectedDocument)
{
ForEach(self.documentItems, id: \.self) { document in
self.checkSearchString(document: document) ? extractedHstack() : emptyView()
}
Extract your hstack and use a trinary with an empty view. Let me know if this works I did this from memory no IDE on this computer.
Related
I have a simple form where user is able to add and delete TextFields, where he/she should be able to insert choices. I want then to process user choices in the next steps. All seems to be working but when I try to remove a field with cursor in it I get a Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range error. It happens only if cursor is in the field which I try to remove(it removes fields with text if no cursor). Found a similar question but there is no option for cursor. How to Have a Deletable List of TextFields in SwiftUI
Thank you for help.
struct ChoicesView: View {
#State var choices: [String] = ["", ""]
var body: some View {
VStack {
Text("Enter your choices").font(.title)
Spacer()
Form {
ForEach(0..<choices.count, id: \.self) { choice in
TextField("", text: Binding(
get:{choices[choice]},
set:{choices[choice] = $0}
)
).textFieldStyle(.roundedBorder)
}
// Buttons
HStack{
if choices.count < 10 {
Button("Add field"){
choices.append("")
print(choices)
}
}
Spacer()
if choices.count > 2 {
Button("Delete field"){
choices.removeLast()
print(choices)
}
}
}.buttonStyle(BorderedButtonStyle())
}
}
}
}
So I have an implementation on a project that someone else has made and I would like to switch the functionality of it, but I am unsure on how to do it, so I wanted to see if someone might be able to help me.
So I have the following code:
TabView(selection: $mainViewModel.selectedItemId) {
ForEach(mainViewModel.selectedCategory == "" ? mainViewModel.places: mainViewModel.places.filter({$0.category == mainViewModel.selectedCategory})) { item in
NavigationLink(
destination: PlaceDetailView(place: item),
label: {
PlaceTabViewDetail(item: item)
.background(Color("ColorWhite")).cornerRadius(20)
.frame(width: getRect().width - 40)
.padding(.horizontal, 10)
.padding(.bottom, 50)
.tag(item.id)
}
) //: END NAVIGATIONLINK
} //: END FOREACH
} //: END TABVIEW
Which outputs this:
How can I extract the "item" and use just a single display, so instead of being able to search through numerous locations tabs, I want just one single display like this:
Basically, I want to utilize "item" since it has all the data, but instead of a foreach, just have a single output.
If you want to show a single item from an array which is currently selected then you can try something like this.
Add a computed property in your MainViewModel.
var selectedItem: Place? {
places.first({ $0.id == selectedItemId })
}
Now use this property in your view like this.
if let item = mainViewModel.selectedItem {
NavigationLink(
destination: PlaceDetailView(place: item),
label: {
PlaceTabViewDetail(item: item)
.background(Color("ColorWhite")).cornerRadius(20)
.frame(width: getRect().width - 40)
.padding(.horizontal, 10)
.padding(.bottom, 50)
.tag(item.id)
}
)
}
I am using Firebase to append a String of data for an Array. I would then like to display this data on the screen. The view on the screen is a List and each Post is looking at a different part of the Array (example; postName[0] vs. postName[1].
My problem is that as soon as the screen loads, the data has not completely loaded from the cloud yet and therefore the Array is empty. This causes the app to crash saying that "Index out of range" since there is nothing in an array that a Text box is trying to read from.
The Array receives data from Firebase and if the data arrives fast enough no issue occurs, however, sometimes the data does not come fast enough and crashes saying index not in range.
Is there anything I can set up to not load the Text field until the data has finished loading?
Code provided:
List(fetchPostModel.postsNearby, id: \.self) { post in
ZStack {
if !fetchPostModel.postName.isEmpty { Text(fetchPostModel.postName[Int(post) ?? 0])
.font(.title)
.bold()
.padding()
} else { Text("Loading...").font(.title).bold().padding() }
}
.onAppear {
fetchFromCloud(postNumber: fetchFromCloud.postNumber[Int(post) ?? 0])
}
}
To prevent the "Index out of range" you can unwrap the property first to avoid the Int(post) ?? 0
if !fetchPostModel.postName.isEmpty {
if let postIndex = post {
Text(fetchPostModel.postName[Int(postIndex)])
.font(.title)
.bold()
.padding()
}
} else { Text("Loading...").font(.title).bold().padding() }
you can create one extension for that
extension Collection where Indices.Iterator.Element == Index {
public subscript(safe index: Index) -> Iterator.Element? {
return (startIndex <= index && index < endIndex) ? self[index] : nil
}
}
Now it can be applied universally across different collections
Example:
[1, 2, 3][safe: 4] // Array - prints 'nil'
(0..<3)[safe: 4] // Range - prints 'nil'
In your problem, you can use like that
List(fetchPostModel.postsNearby, id: \.self) { post in
ZStack {
if let currentPostName = fetchPostModel.postName[safe: post] {
Text(currentPostName)
.font(.title)
.bold()
.padding()
} else {
Text("Loading...").font(.title).bold().padding()
}
}
.onAppear {
fetchFromCloud(postNumber: fetchFromCloud.postNumber[Int(post) ?? 0])
}
}
I am trying to create a dynamic set of TextFields which are added after the user presses the add button. Each press will add another set of those fields. I am new to this so please bear with me. I am getting a fatal error: index out of range. Here is a simple example of what I am trying to achieve.
struct ContentView: View {
#State var name: [String] = []
#State var counter = 0
var body: some View {
Form {
Section {
ForEach(0..<counter, id: \.self) { index in
TextField("Name", text: self.$name[index])
}
Button(action:{
self.counter += 1
}) {
Text("Add more")
}
}
}
}
}
You're increasing the counter without adding new items. If you add a new item to your array it will work without errors:
Button(action:{
self.name.append("")
self.counter += 1
}) {
Text("Add more")
}
But preferably don't use the counter variable at all. If you add a new item to the names array it's count will automatically increase. You can use it in the ForEach loop like this:
ForEach(0..<names.count, id: \.self) { index in
TextField("Name", text: self.$names[index])
}
Button(action:{
self.names.append("")
}) {
Text("Add more")
}
Note: For arrays it's better to use plural names: names instead of name. It indicates it's a collection of items.
SWIFTUI:
Here's the code to show multiple dynamic Text() element:
#State var textsArray = ["a","b","c","d"]
HStack{ ForEach(textsArray, id: \.self)
{ text in
Text("\(text)")
}
}
You can add more texts into "textsArray" or you can change the values in "textsArray" and it'll be automatically changing on UI.
hoping someone can help me out. Been trying to figure out what's going on here with no luck. The app I am building contains the SwiftUI View listed below.
This View is embedded in another View which contains other List's, VStack's, etc. It is called when an item is selected to show another list of data based upon the user's selection.
It all looks, acts and works as intended (without data filtering).
For now, I am using a sample dataSet created using a simple Dictionary of data. When I attempt to apply a filter to this data by string comparison it causes a failure to compile with the following messages:
From Xcode:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
From Canvas:
timedOutSpecific(30.0, operation: "a thunk to build")
In addition to these errors, the energy consumption of Xcode skyrockets until failure.
The code listed below will work if I remove the code self.dataSet == aRecord.module in the if statement and replace it with true. Any time I try to filter my dataset it results in these errors.
import SwiftUI
struct DataListView: View {
#State var titleBar = ""
#State private var showFavorites = false
#State private var showPriority = false
#State var dataSet = ""
var body: some View {
List{
ForEach (sampleData) { aRecord in
if (((aRecord.isFavorite && self.showFavorites) ||
(aRecord.isPriority && self.showPriority) ||
(!self.showPriority)) && self.dataSet == aRecord.module ){
VStack {
NavigationLink(destination: DetailView(titleBar: aRecord.title, statuteData: aRecord.statuteData, isFavorite: aRecord.isFavorite)) {
HStack {
Text(aRecord.module)
.font(.subheadline)
VStack(alignment: .leading) {
Text(aRecord.title)
}
.scaledToFit()
Spacer()
if aRecord.isFavorite {
Image(systemName: "star.fill")
.imageScale(.small)
.foregroundColor(.yellow)
}
}
}
}
.navigationBarTitle(self.titleBar)
.navigationBarItems(trailing:
HStack{
Button(action: {
self.showFavorites.toggle()
}) {
if self.showFavorites {
Image(systemName: "star.fill")
.imageScale(.large)
.foregroundColor(.yellow).padding()
} else {
Image(systemName: "star")
.imageScale(.large).padding()
}
}
Button(action: {
self.showPriority.toggle()
}) {
if self.showPriority {
Text("Priority")
} else {
Text("Standard")
}
}
})
}//endif
}
}//end foreach
}
}
struct TempCode_Previews: PreviewProvider {
static var previews: some View {
DataListView(dataSet: "myDataSetID")
}
}
The reason I believe that the string comparison is the culprit is, for one, it crashes as described above. I have also tried placing the conditional in other places throughout the code with the same results. Any time I apply this type of filter it causes this crash to occur.
Any advice is appreciated.
Thank you.
Break out that complex boolean logic into a function outside of the view builder that takes a record and returns a boolean & it should work.
I think the compiler struggles when there is complex logic inside of the body & can't verify return types etc etc.
Record Verification Function:
func verify(_ record: Record) -> Bool {
return (((record.isFavorite && showFavorites) ||
(record.isPriority && showPriority) ||
(!showPriority)) && dataSet == record.module )
}
Usage In Body:
if self.verify(aRecord) {