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)
}
)
}
Related
I have an app aiming to manage a collection (like pokemon cards). It has JSON data with different variables, in which, the price. To display the list of all items I do the following:
struct itemRow: View {
let items: [Item] = Bundle.main.decode("item.json")
var body: some View {
List {
ForEach(items) { item in
HStack {
Text(item.name)
Text("\(String(format: "%.2f", item.price))€")
}
}
}
}
}
Which creates a straightforward list with names and prices stacked one on top of the other. My issue is that the original data are in €, so when the user uses the app whenever he is in the world, he will see Euro prices. I wanted to do something like an 'if, else' statement or whatever works, to make the user see the correct price even if he has USD. I was able only to show different currencies in the text, like '90$' instead of '90€', but the right conversion is like '97.69$'. I know that rates change every day, but due to the simple app, even some static constants are good enough for my purpose. I would like to support GBP and USD, not every currency
I solved in this way, basing the if-else statement on the Locale
struct itemRow: View {
let items: [Item] = Bundle.main.decode("item.json")
let locale = Locale.current
var body: some View {
List {
ForEach(items) { item in
HStack {
Text(item.name)
if (locale.identifier == "en_US") {
Text("\(String(format: "%.2f", minifig.price * 1.10))$")
.foregroundColor(.gray)
.font(.system(size: 12))
} else if (locale.identifier == "en_GB") {
Text("\(String(format: "%.2f", minifig.price * 0.88))£")
.foregroundColor(.gray)
.font(.system(size: 12))
} else {
Text("\(String(format: "%.2f", minifig.price))€")
.foregroundColor(.gray)
.font(.system(size: 12))
}
}
}
}
}
}
I have built this code
struct StarDifficultyView: View {
var numberOfStarsToShow: Int
var numberOfTotalStarsToShow: Int = 5
var body: some View {
HStack{
var numberLeftToShow = numberOfStarsToShow
ForEach(1..<numberOfTotalStarsToShow+1){_ in
if(numberLeftToShow > 0){
Image(systemName: "star.fill")
.foregroundColor(Color.yellow)
numberLeftToShow -= 1
}else{
Image(systemName: "star.fille")
.foregroundColor(Color.yellow)
}
}
}
}
}
It gives me an error on the line if(numberLeftToShow > 0){ saying "Type '()' cannot conform to 'View'"
Can anyone tell me what I'm doing wrong
Explaining the issue:
You should not add expressions inside the view builder. So numberLeftToShow -= 1 will throw and error because it returns a void ('aka' type()) and this does not conform to View! that is the exact reason for the compiler!
Note 1
Don't use SwiftUI like the UIKit! SwiftUI views may execute over time on any state change and should not be used for calculating anything in this way
Note 2
You can convert 1..<numberOfTotalStarsToShow+1 to a closed range like 1...numberOfTotalStarsToShow (Although you don't need it at all for this question)
Note 3
Try not to use branch and convert your if/else code to something like:
Image(systemName: numberLeftToShow > 0 ? "star.fill" : "star.fille")
.foregroundColor(Color.yellow)
Note 4:
The lower bound of a range can not be less than the upper range, but you can iterate over a reversed range like:
(1...numberOfTotalStarsToShow).reversed()
Note 5:
try using a single source of truth like the forEach parameter itself!
Note 6:
Swift can infer the type and you don't need to pass it again:
so change Color.yellow to .yellow
Final Result:
Here is the code reviewed answer (based on the answer you have provided yourself):
var body: some View {
HStack {
ForEach(1...numberOfTotalStarsToShow, id:\.self) { i in
Image(systemName: "star.fill")
.foregroundColor(i > numberOfStarsToShow ? .gray : .yellow)
}
}
}
Don't throw away the closure parameter for the ForEach!
var body: some View {
HStack{
ForEach(0..<numberOfTotalStarsToShow){ i in // don't ignore the "i" here by writing "_"
// "i" will be different in each "iteration"
// use that to figure out which image to show
if(i < numberOfStarsToShow){
Image(systemName: "star.fill")
.foregroundColor(Color.yellow)
} else {
Image(systemName: "star")
.foregroundColor(Color.yellow)
}
}
}
}
Never mind, I just did this
struct StarDifficultyView: View {
var numberOfStarsToShow: Int
var numberOfTotalStarsToShow: Int = 5
var body: some View {
HStack{
ForEach(1..<numberOfStarsToShow+1){_ in
Image(systemName: "star.fill")
.foregroundColor(Color.yellow)
}
ForEach(1..<numberOfTotalStarsToShow-numberOfStarsToShow+1){_ in
Image(systemName: "star.fill")
.foregroundColor(Color.gray)
.opacity(0.7)
}
}
}
}
Basically, it just loops through the number of yellow stars to show and then works out how many grey ones to show and does another ForEach to display the leftover ones needed
I have a view that gets data from an API what I want to do is rank rows based on points from highest to lowest (I am able to do that now) however issue is that I want to show row number like #1, #2 etc .. as you can see from below image in the red background it says 1 2 for both rows instead of the first saying 1 and the second row saying 2 . This is my code below . I have put a comment of // issue is here which is were the problem lies . As you can see I'm already using a forEach that passes in api data . Is there a way I can use that foreach ForEach(result) and number the rows from 1...n and still be able to retrieve the data like I'm doing . I have seen examples of people doing that but it is usually for a simple string array . Any suggestions would be great .
ScrollView(showsIndicators: false) {
LazyVStack {
let result = model.sorted {
$0.points! > $1.points!
}
ForEach(result) { value in
VStack(alignment: .leading) {
Spacer()
HStack {
ForEach(0..<result.count) { i in
Text("\(i + 1)")
.frame(width: 20, height: 20, alignment: .center)
.foregroundColor(.white)
.background(Color.red)
} // issue is here
Text(value.initials!)
.fontWeight(.bold)
.frame(width: 50, height: 50, alignment: .center)
.foregroundColor(Color(SystemColorPicked(picked: value.color)))
.padding()
.overlay(
Circle()
.stroke(Color.white, lineWidth: 4)
.padding(6)
)
VStack(alignment: .leading, spacing: 1) {
Text("\(value.fullname ?? "NA")")
.foregroundColor(.white)
.fontWeight(.bold)
Text("\(value.points ?? 0) | points")
.foregroundColor(.white)
}
}
}
}
}
}
The problem is you have a ForEach running within in item of your other ForEach. The second one is the one that is displaying "1 2" each time, since it iterates through all the results every time.
I don't have all of your code, so I'm not sure what type results is, but here's a sample that you can adapt to your use case. Keep in mind you'll be doing this to the outer (ie first) ForEach loop:
struct ContentView : View {
#State private var results = ["Test1","Test2","Test3"]
var body: some View {
VStack {
ForEach(Array(results.enumerated()), id: \.1) { (index, value) in
Text("\(index + 1) \(value)")
}
}
}
}
What's going on:
For the ForEach, I'm using results (or, result in your code) .enumerated -- this gives an index/item pair. I'm wrapping it in Array() because otherwise the ForEach won't want to take that specific type of sequence.
ForEach has to have an id -- here I'm just using the String itself. For your purpose, it might be different. .1 refers to the second item of the tuple (ie the item, not the index)
In the ForEach closure, you now get index and value
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.
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) {