Change button/label text using key:value from local JSON - SwiftUI - arrays

New to SwiftUI. Trying to get a JSON key:value array to update to the next random item when the user presses the button. Got it to load up just fine, but the button does nothing. Tried making a shuffle function, but couldn't find a way to pass in the new values to the Text areas. Also tried to make my decodedQuotes and quote variables into #State vars inside the View, but they initialize before self is available.
Could normally call touchesBegan and write a simple function in Storyboard. Is there something similar I could do here?
var decodedQuotes = Bundle.main.decode([Quote].self, from: "quotes.json")
// parses an array with "quote":"name" pairs
var quote = decodedQuotes.randomElement()!
struct QuoteView: View {
var body: some View {
Button(action:
// Need it to update the Text below with a new random item from quote
)
HStack {
VStack {
HStack(alignment: .center) {
Text(quote.quote)
.multilineTextAlignment(.center)
.padding()
.foregroundColor(.black)
}
HStack {
Text("-\(quote.name)")
.foregroundColor(.black)
}
}
}
.frame(width: 300, height: 300, alignment: .center)
.background(Background(isHighlighted: true, shape: Rectangle()))
.foregroundColor(.blue)
.padding(4)
.cornerRadius(20)
}
}

You were on the right track with #State
struct Quote {
var quote : String
var name : String
}
var decodedQuotes = [Quote(quote: "test1", name: "name1"),
Quote(quote: "test2", name: "name2"),
Quote(quote: "test3", name: "name3"),]
struct QuoteView: View {
#State var quote : Quote? = decodedQuotes.randomElement()
var body: some View {
Button(action: {
quote = decodedQuotes.randomElement()
}) {
Text("New quote")
}
if let quote = quote {
HStack {
VStack {
HStack(alignment: .center) {
Text(quote.name)
.multilineTextAlignment(.center)
.padding()
.foregroundColor(.black)
}
HStack {
Text("-\(quote.name)")
.foregroundColor(.black)
}
}
}
.frame(width: 300, height: 300, alignment: .center)
.foregroundColor(.blue)
.padding(4)
.cornerRadius(20)
}
}
}
Obviously, for testing, I just used an array of pre-made Quotes
If you wanted to, you could make decodedQuotes a #State property on the QuoteView as well and decode them in onAppear
I've also chosen to make quote an optional for now. I check to see if it's available by doing the if let quote = quote line. This should be a bit future-proof in case you start loading quotes from other places at some point.

I believe this is a better implementation in the current SwiftUI where the text actually changes within the button. I hope it helps>
import SwiftUI
struct Quote {
var quote : String
var name : String
}
var decodedQuotes = [Quote(quote: "Title 1", name: "Description 1."),
Quote(quote: "Title 2", name: "Second description."),
Quote(quote: "Title 3", name: "final item."),]
struct ContentView: View {
#State var quote : Quote? = decodedQuotes.randomElement()
var body: some View {
Button(action: {
quote = decodedQuotes.randomElement()
}) {
Text("New quote")
if let quote = quote {
HStack {
VStack {
VStack(alignment: .center) {
Text(quote.quote)
.multilineTextAlignment(.center)
.padding()
.foregroundColor(.blue)
Text("-\(quote.name)")
.foregroundColor(.blue)
}
}
}
.frame(width: 300, height: 300, alignment: .center)
.foregroundColor(.blue)
.padding(4)
.cornerRadius(20)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

How would I delete items from a list with a button inside that list?

SwiftUI
[This is the code for the list and my button]
ForEach(items, id: \.self) { current in
VStack(alignment: .leading) {
Text("\(current.task)")
.padding(2)
Text("\(dateFormatter.string(from: current.date))")
Button(action: {},
label: {
Image(systemName:
"checkmark.rectangle.fill")
.resizable()
.frame(width: 50, height: 35)
})
}
Items is my array containing a string and a date. Here is the code for it:
#State var items:[Tasks] = [Tasks(task: "Test", date: Date())]
And here is Tasks:
struct Tasks: Hashable {
let task: String
let date: Date
}
This is my list view
I want to have a user be able to click a button under each list item and it will remove that list item. I am currently using the onDelete method but I would prefer to have a confirm button in each list item that would allow the user to remove that list item.
#State var counter = -1
I tried using a counter that would increase by 1 each time the ForEach loop ran and create a new variable inside of that ForEach loop equal to it. However I could not access the variable inside of the button to use as an index.
Using your current code structure, assuming each task: String is unique, you could just add this to your Button action:
Button(action: {
if let index = items.firstIndex(where: {$0.task == current.task}) {
items.remove(at: index)
}
},
label: {
Image(systemName: "checkmark.rectangle.fill")
.resizable()
.frame(width: 50, height: 35)
})
However, it would be more robust to add an id property to your Task, and
at the same time rename it, because Swift already defines Task as part of its async/await framework.
Here is the complete code I use to test my answer:
struct MyTask: Identifiable, Hashable { // <--- here
let id = UUID() // <--- here
let task: String
let date: Date
}
struct ContentView: View {
#State var items:[MyTask] = [
MyTask(task: "Test1", date: Date()),
MyTask(task: "Test2", date: Date().addingTimeInterval(60*60*24*44)),
MyTask(task: "Test3", date: Date().addingTimeInterval(60*60*24*88))
]
var body: some View {
ForEach(items) { current in // <--- here
VStack(alignment: .leading) {
Text(current.task).padding(2) // <--- here
Text(dateFormatter.string(from: current.date)) // <--- here
Button(action: {
// --- here
if let index = items.firstIndex(where: {$0.id == current.id}) {
items.remove(at: index)
}
},
label: {
Image(systemName: "checkmark.rectangle.fill").resizable()
.frame(width: 50, height: 35)
})
}
}
}
var dateFormatter: DateFormatter = {
let frmt = DateFormatter()
frmt.dateFormat = "yyyy-MMM-dd"
return frmt
}()
}

SwiftUI: bound string into array of strings

I have a variable "textEditorText" bound to the "TextEditor".
When the button "send to the player" is clicked, the entire content of the text editor is transferred to the "stringArray" array.
But in this array, all the text is contained as one element, and I need to split it into words.
For example, the sentence "one, two, three" needs to be turned into the elements "one", "two", "three
I'm trying to do this by passing the content from stringArray to stringArray2 but I'm getting the error messages
How can I overcome this situation?
Thank you.
import SwiftUI
struct ContentView: View {
#State var textEditorText:String = "one two three"
#State var stringArray:[String] = []
var stringArray2:[String] = stringArray.components(separatedBy: " ")
var body: some View {
NavigationView {
VStack{
TextEditor(text: $textEditorText)//binding to #State var textEditorText
.frame(height: 200)
.cornerRadius(10)
Button (action: {
textEditorText = ""
}, label: {
Text("clear the editor".uppercased())
.font(.headline)
//.foregroundColor(.white)
.padding(10)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(10)
})
Button (action: {
if stringArray.isEmpty && !textEditorText.isEmpty {
stringArray.append(textEditorText)
}
}, label: {
Text("send to the player".uppercased())
.font(.headline)
.padding(10)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
//.background(stringArray.isEmpty ? Color.red : Color.blue)
.background(Color.blue)
.cornerRadius(10)
})
Button(action: {
stringArray.removeAll()
}, label: {
Text("clear the player".uppercased())
.padding(10)
.frame(maxWidth: .infinity)
.background(Color.blue.cornerRadius(10))
.foregroundColor(.white)
.font(.headline)
.shadow(radius: 5)
})
ForEach(stringArray, id: \.self) { data in
Text(data)
}
.frame(maxWidth:.infinity, maxHeight: 30)
Spacer()
} //VStack
.padding(10)
.background(Color.gray.opacity(0.25))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
There's a lot of unnecessary code in your question, but if I understand correctly what you're trying to do, you just need to change it to use
stringArray.append(contentsOf: )
rather than
stringArray.append()
for example:
if stringArray.isEmpty && !textEditorText.isEmpty {
stringArray.append(contentsOf: textEditorText.components(separatedBy: " "))
}

Sheetview with zipped arrays

Problem:
I the sheet view only opens the first item in the arrays. I've looked and found a few similar answers, but they don't deal with zipping arrays together. They mostly have one array. Here is one example. I've also tried to extract the item to a string, and then use the string instead of the item.0, or item.1.
If anyone has any suggestions, it would help a lot.
Main Code:
ScheduledAirplanes = ["1", "2", "3", "4"]
ScheduledInstructors = ["5", "6", "7", "8"]
ScheduledstartTime = ["9", "10", "11", "12"]
ScheduledDate = ["1/2", "1/3", "1/4", "1/5"]
#State private var showingSheet = false
var arraysForLoop : [(String,String,String,String)] {
let result = zip(ScheduledAirplanes,zip(ScheduledInstructors,zip(ScheduledstartTime,ScheduledDate)))
return result.map { ($0.0, $0.1.0, $0.1.1.0, $0.1.1.1) }
}
ForEach(arraysForLoop, id: \.0) { item in
VStack {
Group {
Text("Time")
VStack {
Text(item.3)
.font(.caption)
.foregroundColor(.gray)
Text(item.2)
.font(.caption)
.foregroundColor(.gray)
}
.padding([.leading, .trailing])
Divider()
.padding([.leading, .trailing])
Text("Flight")
Text(item.0)
.font(.caption)
.foregroundColor(.gray)
Text("Instructor")
Text(item.1)
.font(.caption)
.foregroundColor(.gray)
}
}
.onTapGesture {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
FlightInfo(Airplane: "\(item.0)", Instructor: "\(item.1)")
}
.frame(width: 110, height: 140)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(.gray.opacity(0.3), lineWidth: 1)
}
Spacer()
}
Here is the FlightView code:
struct FlightInfo: View {
#Environment(\.dismiss) var dismiss
#State var Airplane: String
#State var Instructor: String
var body: some View {
VStack {
HStack {
Text("Flight Info")
.font(.title)
.fontWeight(.bold)
Spacer()
Button("Cancel") {
dismiss()
}
}
.padding()
VStack {
Text("\(Airplane)")
.font(.caption)
.fontWeight(.medium)
.foregroundColor(.gray)
Text("\(Instructor)")
.font(.caption)
.fontWeight(.medium)
.foregroundColor(.gray)
}
Spacer(minLength: 0)
Button(action: {
dismiss()
}) {
Text("Done")
}
.foregroundColor(.white)
.frame(width: 120, height: 45)
.background(Color("Blue"))
.cornerRadius(30)
.padding(.bottom)
}
}
}
I would strongly suggest that you rethink modeling your data by having a set of different String arrays that all correspond together by index. Instead, modeling your data with something like this would be much more friendly to work with in SwiftUI:
struct Model {
var scheduleAirplane : String
var scheduledInstructor: String
var scheduledStartTime: String
var scheduledDate: String
}
extension Model : Identifiable {
var id : String { scheduleAirplane }
}
If for some reason you're stuck with the separate arrays, you can still take advantage of the more friendly models by doing something like I have below. Note that I've kept your zip code and used to to create the models. The other important difference is that I'm using sheet(item:) and passing a reference to the correct item. That gets handled by a single sheet modifier that takes a reference to the item.
struct ContentView: View {
var scheduledAirplanes = ["1", "2", "3", "4"]
var scheduledInstructors = ["5", "6", "7", "8"]
var scheduledStartTime = ["9", "10", "11", "12"]
var scheduledDate = ["1/2", "1/3", "1/4", "1/5"]
#State private var sheetItem : Model?
var arraysForLoop : [Model] {
let result = zip(scheduledAirplanes,zip(scheduledInstructors,zip(scheduledStartTime,scheduledDate)))
return result.map { Model(scheduleAirplane: $0.0, scheduledInstructor: $0.1.0, scheduledStartTime: $0.1.1.0, scheduledDate: $0.1.1.1) }
}
var body: some View {
ForEach(arraysForLoop) { item in
VStack {
Group {
Text("Time")
VStack {
Text(item.scheduledDate)
.font(.caption)
.foregroundColor(.gray)
Text(item.scheduledStartTime)
.font(.caption)
.foregroundColor(.gray)
}
.padding([.leading, .trailing])
Divider()
.padding([.leading, .trailing])
Text("Flight")
Text(item.scheduleAirplane)
.font(.caption)
.foregroundColor(.gray)
Text("Instructor")
Text(item.scheduledInstructor)
.font(.caption)
.foregroundColor(.gray)
}
}
.onTapGesture {
sheetItem = item
}
.frame(width: 110, height: 140)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(.gray.opacity(0.3), lineWidth: 1)
}
Spacer()
}.sheet(item: $sheetItem) { item in
FlightInfo(Airplane: "\(item.scheduleAirplane)", Instructor: "\(item.scheduledInstructor)")
}
}
}
Note that if you took the first suggestion and coalesced the arrays into models first, the beginning of the View would just look like this:
struct ContentView: View {
var models = [Model]
#State private var sheetItem : Model?
var body: some View {
ForEach(models) { item in
//etc

Looping multiple arrays with ForEach SwiftUI

UI: https://imgur.com/a/0BbJBFc
I'm using the ForEach to iterate over an array of minerals in the example code, but I can't find a proper solution to loop the second array (mineral amount) right underneath the minerals.
In different project, I made it so far that the ForEach loops both arrays but for every mineral displays all amount for the Planet and for the second mineral all amount for the Planet and so on.
I did create a new struct with both arrays but without success. Adding a binding property also failed. I hope to learn a new swift method to achieve the desire look.
Data File
import Foundation
struct Planet: Identifiable {
var id = UUID()
var name: String
var minerals: [String]
var mineralAmount: [String]
}
let planetsData: [Planet] = [
Planet(name: "Jupiter", minerals: ["Iron", "Gold", "Copper"], mineralAmount: ["10K", "20K", "30K"]),
Planet(name: "Earth", minerals: ["Lithium", "Aluminium", "Quartz"], mineralAmount: ["40K", "50K", "60K"])
]
ContentView
import SwiftUI
struct ContentView: View {
var planet: Planet
var body: some View {
VStack() {
ForEach(planet.minerals, id: \.self) { item in
Text(item)
.font(.system(size: 22, weight: .regular))
.foregroundColor(.primary)
Text("amount to be added")
.font(.system(size: 22, weight: .regular))
.foregroundColor(.primary)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(planet: planetsData[0])
}
}
[1]: https://i.stack.imgur.com/4MDKs.png
You can achieve it this way:
ScrollView {
VStack (alignment: .leading) {
ForEach(0..<planet.minerals.count) { i in
HStack {
Circle()
.frame(width: 50, height: 50)
.foregroundColor(.black)
VStack (alignment: .leading) {
Text(planet.minerals[i])
.font(.system(size: 22, weight: .regular))
.foregroundColor(.primary)
Text(planet.mineralAmount[i])
.font(.system(size: 22, weight: .regular))
.foregroundColor(.secondary)
}
}
}
Spacer()
}
}
why don't you create a dictionary from both values
mineralsDic = [minerals: mineralAmount]
I of course know the syntax of dictionary but I just try to explain my idea + instead of making 2 loops you can make only one which has less complexity and better performance

on TabGesture Swift UI

look for some help on my study of the scrollView in swiftUI.
I have a scroll view that display the value of an array, base when the user tap on the different item of scroll view I want to display it on the textField below.
how can I pass the array value to the text field??
import SwiftUI
struct ContentView: View {
let post = ["TEST1 ","Test 2" , "Test 3","TEST4 ","Test 5" , "Test 6"]
var temp = ""
var body: some View {
VStack {
ScrollView(.horizontal, content: {
HStack(spacing: 100) {
ForEach(post, id: \.self){ item in
ZStack {
Rectangle().foregroundColor(.blue).frame(width: 190, height: 170, alignment: .center)
Text(item)
}.onTapGesture {
// pass the value off Scroll View to the text
debugPrint("\(item)")
}
}
}
.padding(.leading, 10)
})
.frame(height: 190)
Spacer()
Text("dispaly here array value selected")
Spacer()
}
}
}
thank for helping me...
The trick here is you need to #State temp when you need to assign to a #State value inside the view.
struct ContentView: View {
let post = ["TEST1 ","Test 2" , "Test 3","TEST4 ","Test 5" , "Test 6"]
#State private var temp = ""
var body: some View {
VStack {
ScrollView(.horizontal, content: {
HStack(spacing: 100) {
ForEach(post, id: \.self){ item in
ZStack {
Rectangle().foregroundColor(.blue).frame(width: 190, height: 170, alignment: .center)
Text(item)
}.onTapGesture {
// pass the value off Scroll View to the text
self.temp = item
}
}
}
.padding(.leading, 10)
})
.frame(height: 190)
Spacer()
Text( self.temp)
Spacer()
}
}
}

Resources