SwiftUI: retrieving and displaying boolean literal values - boolean

I have a data model that includes a couple of booleans. I get the following error: "Initializer 'init(_:)' requires that 'Bool' conform to 'StringProtocol'" when I try to retrieve and display the booleans. I am a total novice to SwiftUI and any help is greatly appreciated
import SwiftUI
struct EventRow: View {
var event: Event
var dateFormatter: DateFormatter{
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}
var body: some View{
VStack{
HStack{
Text("Date: ").bold() + Text(self.dateFormatter.string(from: event.eventDate))
Text("Details: ").bold() + Text(event.eventDetails).italic()
}.foregroundColor(.blue)
HStack{
Text("Category: ").bold() + Text(event.eventCategory)
Text("Time: ")
.bold()
+ Text(event.eventDuration)
}.font(.footnote)
HStack{
Text("In-House?: ").bold()
+ Text(event.eventInHouse)
Text("At-Nite? : ")
.bold()
+ Text(event.eventNite)
}
}
}
}

There is no Event provided, but I assume it is for eventInHouse and similar, so you can use like
Text("In-House?: ").bold()
+ Text(event.eventInHouse ? "Yes" : "No")

Related

How to show a different price value and currency based on the country

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))
}
}
}
}
}
}

Swift HStack View not conforming to protocol 'View'

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

TextField that accepts only numbers and one decimal point in SwiftUI

I searched for a good approach for this problem and none of the questions I'v found answered my needs. I'm struggling with this.
What I need is a TextField that would accept only 0123456789. with format of %.2f and wouldn't require to press Return key to submit. Receiving the value as Double and return as Double.
The results should be 123.45 or 1.23.
My attempt:
var title: String
let numberFormatter = NumberFormatter()
#Binding var value: Double
var amountProxy: Binding<String> {
Binding<String>(
get: { String(format: "%.2f", value) },
set: {
if let value = numberFormatter.number(from: $0) {
self.value = value.doubleValue
}
}
)
}
var body: some View{
HStack(spacing: 8){
Text(LocalizedStringKey(title + ":"))
.layoutPriority(1)
TextField(LocalizedStringKey(""), text: amountProxy)
.keyboardType(.decimalPad)
.multilineTextAlignment(.trailing)
}
.lineLimit(1)
}
This method results were forcing the user to type one number and move precisely after the decimal point to get the requested value 4.20. Something like type, move, repeat. Very poor UX.

Swift : Filter in the dictionary that is grouped by key

My goal is to be able to filter by name in the dictionary that is grouped by key and to see the results filtered by the text that was inputted in the Search Field. What I stopped at is:
var groupedContacts: [String: [CNContact]] {
.init (
grouping: store.contacts,
by: {$0.nameFirstLetter}
)
}
func filterContactsByName(_ textSearch: String) -> [String: [CNContact]] {
let contacts = groupedContacts
if !textSearch.isEmpty {
return contacts.compactMapValues{$0.filter {$0.name.localizedCaseInsensitiveContains(textSearch)}}.filter{!$0.value.isEmpty}
} else {
return contacts
}
}
And then:
List() {
ForEach(self.filterContactsByName(searchText).keys.sorted(), id: \.self) { key in
Section(header: Text(key).modifier(SectionHeader(backgroundColor: Color.white, foregroundColor: Color.black))) {
ForEach(self.groupedContacts[key]!, id: \.self) { contact in
HStack {
self.image(for: contact.imageProfile)
.renderingMode(.original)
.resizable()
.scaledToFill()
.frame(width: 40, height: 40)
.aspectRatio(contentMode: .fit)
.clipShape(Circle())
My result:
screenshot
As I understood, I received all the results in the key-group, that's why we see Danial before David. And it will be the same if we input David - we will see Danial.
TIA for a help.
You need to both filter out the right group and then filter inside that group. In the below code, which is somewhat simplified since I used String instead of CNContact and didn't bother with case insensitive searching, I first find the group by filtering on the first char of the search string and then I used reduce(into:) in combination with filter to filter the array for the found key.
var groupedContacts: [String: [String]] = ["D": ["David", "Daniel"], "A": ["AAA"]]
let filter = "Dav"
let results = groupedContacts.filter { $0.key == filter.prefix(1) }
.reduce(into: [:]) {$0[$1.key] = $1.value.filter { $0.hasPrefix(filter)}}

Breaking up the expression into distinct sub-expressions in a ForEach/ZStack (SwiftUI)

I have a little problem on the lastest SwiftUI, the error is "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
My code is like that :
let cards = ["Azertyuiop", "Bzertyuiop", "Czertyuiop", "Dzertyuiop", "Ezertyuiop", "Fzertyuiop", "Gzertyuiop", "Hzertyuiop", "Izertyuiop", "Jzertyuiop", "Kzertyuiop", "Lzertyuiop", "Bzertyuiop", "Czertyuiop", "Dzertyuiop", "Ezertyuiop", "Fzertyuiop", "Gzertyuiop", "Lzertyuiop", "Bzertyuiop", "Czertyuiop", "Dzertyuiop", "Ezertyuiop", "Fzertyuiop", "Gzertyuiop", "Lzertyuiop", "Bzertyuiop", "Czertyuiop", "Dzertyuiop", "Ezertyuiop", "Fzertyuiop", "Gzertyuiop"]
var body: some View {
ScrollView{
VStack (spacing: 0, content: {
ForEach(0..<cards.count/3) { row in // create number of rows
HStack (spacing: 0, content: {
ForEach(0..<3) { column in // create 3 columns
ZStack(alignment: .bottomLeading){
Image("ghost").resizable().aspectRatio(contentMode: .fill)
.overlay(Rectangle().fill (LinearGradient(gradient: Gradient(colors: [.clear, .black]),startPoint: .center, endPoint: .bottom)).clipped())
Text(self.cards[row * 3 + column]) // this cause the error
.fontWeight(.semibold)
}
}
})
}
})
}
}
I guess that the error comes from : row * 3 + column
So I tried to put the integer 1 instead of this calculation, and it worked.
How to do this calculation in my body and my View? because SwiftUI does not allow me and shows me "Expected pattern"
Thanks a lot !
There are two problems. One is using ZStack with overlay, the other is Linear Gradient is a view and you can use it directly.
ZStack(alignment: .bottomLeading){
Image("ghost").resizable().aspectRatio(contentMode: .fill)
LinearGradient(gradient: Gradient(colors: [.clear, .black]),startPoint: .center, endPoint: .bottom).clipped()
Text(self.cards[row * 3 + column]) // this cause the error
.fontWeight(.semibold)
}