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: " "))
}
Related
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
I create the empty savedList in my ViewModel
#Published var savedList:[Categories] = [Categories]()
Then I create the code so that any restaurant can be saved and then it is added to the array
struct DetailView2: View {
var restaurantInfo:Categories
#EnvironmentObject var a:CategoriesModel
#State var isSaved = false
var body: some View {
VStack {
ZStack(alignment: .bottomLeading) {
Image(restaurantInfo.pic)
.resizable()
.scaledToFit()
Button(action: {
if isSaved == true {
let i = a.savedList.firstIndex(of: restaurantInfo)
a.savedList.remove(at: i!)
isSaved = false
} else if isSaved == false {
a.savedList.append(restaurantInfo)
isSaved = true
}
}, label: {
if a.savedList.contains(restaurantInfo) {
Image(systemName: "bookmark.fill")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
.padding()
} else {
Image(systemName: "bookmark")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
.padding()
}
})
}
This code saves the restaurants properly, but how do I make it so that the array remains saved even after any particular user exits the app and re enters it. #AppStorage doesn't work for 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()
}
}
This is a very similar problem to one I had before (which no one could answer). I'm trying to create a dynamic list in which I can edit elements. As far as I can gather, the recommended way to do this is to have an EditView, with bindings, that's activated by a NavigationLink in the LIst.
So, I've done that. It appears to work at first, until I realised that each NavigationLink would only work once (is this a bug?). I can't think what I could have done wrong to cause that.
Then I thought perhaps I can switch to in-place editing by having the EditView in the List. I devised a theoretical way to do this, then tried it in my code. And at first it seemed to work great. However, if 'edit in place' is on, deleting the last element causes 'Fatal error: Index out of range'.
I've bundled my whole code into one file so you can just copy and paste into Xcode to try for yourself.
I'm starting to think that maybe XCode 11.3.1 is far from the finished article, yet.
import SwiftUI
struct EditView: View {
#Binding var person:Person
var body: some View {
HStack{
Group{
TextField("name1", text: $person.name1)
TextField("name2", text: $person.name2)
}.frame(width:200)
.font(.headline)
.padding(.all, 3)
.overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.blue, lineWidth: 1))
}.navigationBarTitle("Edit entry")
}
}
struct Person:Identifiable, Equatable{
var id:UUID
var name1:String
var name2:String
var isEditable:Bool
}
class PersonList: ObservableObject {
#Published var individuals = [Person]()// Array of Person structs
}
struct ContentView: View {
#ObservedObject var people = PersonList()// people.individuals = [Person] array
#State private var edName1:String = "" //temporary storage for adding new member
#State private var edName2:String = "" //temporary storage for adding new member
#State private var allowEditing:Bool = false
var elementCount:Int{
let c = people.individuals.count
return c
}
// arrays for testing - adds random names from these (if input field '1st name' is empty)...
var firstNames = ["Nick","Hermes","John","Hattie","Nicola","Alan", "Dwight", "Richard","Turanga", "Don","Joey"]
var surnames = ["Farnsworth","Fry","Wong","Zoidberg","Conrad","McDougal","Power","Clampazzo","Brannigan","Kroker","Leela"]
var body: some View {
NavigationView{
VStack{
HStack{
Text("Add person:")
.padding(.all, 5)
.frame(alignment: .leading)
TextField("1st name", text: $edName1)
.frame(width:150)
.padding(.all, 5)
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.blue, lineWidth: 2))
TextField("2nd name", text: $edName2)
.frame(width:150)
.padding(.all, 5)
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color.blue, lineWidth: 2))
// 🆗 Button...
Image(systemName: "plus.circle")
.font(.largeTitle)
.foregroundColor(.orange)
.onTapGesture {
if self.edName1 == ""{
self.edName1 = self.firstNames.randomElement() ?? "⁉️"
self.edName2 = self.surnames.randomElement() ?? "⁉️"
}
self.people.individuals.append(Person(id: UUID(), name1: self.edName1, name2: self.edName2, isEditable: false))
self.edName1 = ""
self.edName2 = ""
print("Element count: \(self.elementCount)")
}
Toggle(isOn: $allowEditing){Text("edit in place")}.padding(.all,5).overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color.red, lineWidth: 2))
Spacer()
// 🆗 Button...sort
Image(systemName: "arrow.up.arrow.down.square")
.font(.title)
.padding(.all,4)
.foregroundColor(.blue)
.onTapGesture {
self.people.individuals.sort{ // sort list alphabetically by name2
$0.name2 < $1.name2
}
}
// 🆗 Button...reverse order
Image(systemName: "arrow.uturn.up.square")
.font(.title)
.padding(.all,8)
.foregroundColor(.blue)
.onTapGesture {
self.people.individuals.reverse()
}
}.padding(.all,8)
.overlay(RoundedRectangle(cornerRadius: 12)
.stroke(Color.orange, lineWidth: 2))
List{
ForEach(people.individuals){individual in
HStack{
if self.allowEditing{
//Toggle to edit in place
Toggle(isOn: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!].isEditable){
Text("edit").font(.headline).foregroundColor(.green).opacity(individual.isEditable ? 1.0 : 0.4)
}.frame(width:100)
}
if individual.isEditable{
EditView(person: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!])
}
else{
NavigationLink(destination:EditView(person: self.$people.individuals[self.people.individuals.firstIndex(of:individual)!])){
Text("\(individual.name1) \(individual.name2)")
.frame(width: 200, alignment: .leading)
.padding(.all, 3)
}// link
}
}
}.onDelete(perform: deleteRow)
}
}.navigationBarTitle("People List (\(elementCount))")
}.navigationViewStyle(StackNavigationViewStyle())
}
func deleteRow(at offsets: IndexSet){
self.people.individuals.remove(atOffsets: offsets)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.colorScheme, .dark)
}
}
Can anyone shed any light on this? I can't find anything to help me.
UPDATE: Thanks to 'krjw' for pointing out the single use NavLink problem does not happen on a real device.
The 'last element delete' issue seems to be something to do with an active binding being present in the element's view.
Ok despite my comment I tried to get to a solution and I might found an acceptable one:
I had to remodel Person... The whole indices was the issue of course but I couldn't exactly find out when what happens. I even tried with a local #State which updates the view and then updates the array of the #ObservedObject...
here are some links which could help to further investigate though...
Swift UI detail remove
How do I set the toggle state in a foreach loop in SwiftUI
Also this link here shows how to update members of an observed array generically which is pretty cool!:
https://stackoverflow.com/a/57920136/5981293
struct EditView: View {
#ObservedObject var person: Person
var body: some View {
HStack{
Group{
TextField("name1", text: $person.name1)
TextField("name2", text: $person.name2)
}//.frame(width:200)
.font(.headline)
.padding(.all, 3)
.overlay(RoundedRectangle(cornerRadius: 4).stroke(Color.blue, lineWidth: 1))
}.navigationBarTitle("Edit entry")
}
}
struct RowView: View {
#Binding var allowEditing: Bool
#ObservedObject var individual: Person
var body: some View {
HStack {
if self.allowEditing {
//Toggle to edit in place
Toggle(isOn: self.$individual.isEditable){
Text("edit").font(.headline).foregroundColor(.green).opacity(self.individual.isEditable ? 1.0 : 0.4)
}//.frame(width:100)
}
if self.individual.isEditable{
EditView(person: self.individual)
}
else{
NavigationLink(destination:EditView(person: self.individual)){
Text("\(self.individual.name1) \(self.individual.name2)")
//.frame(width: 200, alignment: .leading)
.padding(.all, 3)
}// link
}
}
}
}
class Person: ObservableObject, Identifiable {
#Published var id:UUID
#Published var name1:String
#Published var name2:String
#Published var isEditable:Bool
init(id: UUID, name1: String, name2: String, isEditable: Bool){
self.id = id
self.name1 = name1
self.name2 = name2
self.isEditable = isEditable
}
}
struct ContentView: View {
#State var people = [Person]()//try! ObservableArray<Person>(array: []).observeChildrenChanges(Person.self)// people.individuals = [Person] array
#State private var edName1:String = "" //temporary storage for adding new member
#State private var edName2:String = "" //temporary storage for adding new member
#State private var allowEditing:Bool = false
// arrays for testing - adds random names from these (if input field '1st name' is empty)...
var firstNames = ["Nick","Hermes","John","Hattie","Nicola","Alan", "Dwight", "Richard","Turanga", "Don","Joey"]
var surnames = ["Farnsworth","Fry","Wong","Zoidberg","Conrad","McDougal","Power","Clampazzo","Brannigan","Kroker","Leela"]
var body: some View {
NavigationView{
VStack{
HStack{
Text("Add person:")
.padding(.all, 5)
.frame(alignment: .leading)
TextField("1st name", text: $edName1)
//.frame(width:150)
.padding(.all, 5)
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.blue, lineWidth: 2))
TextField("2nd name", text: $edName2)
//.frame(width:150)
.padding(.all, 5)
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color.blue, lineWidth: 2))
// 🆗 Button...
Image(systemName: "plus.circle")
.font(.largeTitle)
.foregroundColor(.orange)
.onTapGesture {
if self.edName1 == ""{
self.edName1 = self.firstNames.randomElement() ?? "⁉️"
self.edName2 = self.surnames.randomElement() ?? "⁉️"
}
self.people.append(Person(id: UUID(), name1: self.edName1, name2: self.edName2, isEditable: false))
self.edName1 = ""
self.edName2 = ""
print("Element count: \(self.people.count)")
}
Toggle(isOn: $allowEditing){Text("edit in place")}.padding(.all,5).overlay(RoundedRectangle(cornerRadius: 8)
.stroke(Color.red, lineWidth: 2))
Spacer()
// 🆗 Button...sort
Image(systemName: "arrow.up.arrow.down.square")
.font(.title)
.padding(.all,4)
.foregroundColor(.blue)
.onTapGesture {
self.people.sort{ // sort list alphabetically by name2
$0.name2 < $1.name2
}
}
// 🆗 Button...reverse order
Image(systemName: "arrow.uturn.up.square")
.font(.title)
.padding(.all,8)
.foregroundColor(.blue)
.onTapGesture {
self.people.reverse()
}
}.padding(.all,8)
.overlay(RoundedRectangle(cornerRadius: 12)
.stroke(Color.orange, lineWidth: 2))
List {
ForEach(self.people) { person in
RowView(allowEditing: self.$allowEditing, individual: person)
}.onDelete(perform: deleteRow)
}
}.navigationBarTitle("People List (\(self.people.count))")
}.navigationViewStyle(StackNavigationViewStyle())
}
func deleteRow(at offsets: IndexSet){
self.people.remove(atOffsets: offsets)
print(self.people.count)
}
}
I hope this helps!
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()
}
}
}