How to mutate the variable that passed from other views - swift

I am new to the SwiftUI, I try to create an app, it has a list of goals and above the list, there is an add button to add a goal and display it on the list. Currently, I am having trouble adding the goal instance into the goals(array of goals), in the create view, I try to append a new instance of Goal to the goals that I created in another view. And it gives me an error message: Cannot use mutating member on immutable value: 'self' is immutable on the line goals.append(Goal(...)) Does anyone know how to fix it? here is my code! Thank you so much!
struct ContentView: View {
var goals: [Goal] = []
var body: some View {
TabView{
VStack{
Text("You have")
Text("0")
Text("tasks to do")
}.tabItem { Text("Home")}
MyScroll(1..<100).tabItem { Text("My Goals") }
}
}
}
struct MyScroll: View {
var numRange: Range<Int>
var goals: [Goal]
init (_ r:Range<Int>) {
numRange = r
goals = []
}
var body: some View {
NavigationView{
VStack{
NavigationLink(destination: AddView(goals:self.goals)){
Image(systemName: "folder.badge.plus")
}
List(goals) { goal in
HStack(alignment: .center){
Text(goal.name)
}
}
}
}.navigationTitle(Text("1111"))
}
}
struct AddView: View {
var goals:[Goal]
#State var types = ["study", "workout", "hobby", "habbit"]
#State private var selected = false
#State var selection = Set<String>()
#State var goalName: String = ""
#State var goalType: String = ""
#State var isLongTerm: Bool = false
#State var progress: [Progress] = []
var body: some View {
VStack{
Text("Create your goal")
// type in name
HStack{
TextField("Name", text: $goalName)
}.padding()
// choose type: a selection list
HStack{
List(types, id: \.self, selection: $selection) {
Text($0)
}
.navigationBarItems(trailing: EditButton())
}.padding()
// toggle if it is a logn term goal
HStack{
Toggle(isOn: $selected) {
Text("Is your goal Long Term (no end date)")
}.padding()
}.padding()
Button(action: {
addGoal(goalName, goalType, isLongTerm, progress)
}, label: {
/*#START_MENU_TOKEN#*/Text("Button")/*#END_MENU_TOKEN#*/
})
}
}
// function that add the goal instance to the goals
mutating func addGoal( _ t:String, _ n:String, _ iLT: Bool, _ p: [Progress]){
let item: Goal = Goal(t,n,iLT,[])
goals.append(item)
}
}
The Goal is just a structure that I created for storing information:
import Foundation
// This is the structure for each goal when it is created
struct Goal: Identifiable {
var id: UUID
var type: String // type of goals
var name: String // the custom name of the goal
var isLongTerm: Bool // if goal is a long term goal (no deadline)
var progress: [Progress] // an array of progress for each day
init(_ t:String, _ n:String, _ iLT: Bool, _ p: [Progress]) {
id = UUID()
type = t
name = n
isLongTerm = iLT
progress = p
}
}

One way to to this is by using a #Binding to hold #State in a parent view and pass it down through the view hierarchy, letting the children send data back up.
(One caveat is that sending a Binding through many views looks like it may have unexpected results in the current version of SwiftUI, but one or two levels seems to be fine. Another option is using an ObservableObject with a #Published property that gets passed between views)
Note how the ContentView owns the [Goal] and then the subsequent child views get it as a #Binding -- the $ symbol is used to pass that Binding through the parameters:
struct Goal: Identifiable {
var id: UUID
var type: String // type of goals
var name: String // the custom name of the goal
var isLongTerm: Bool // if goal is a long term goal (no deadline)
var progress: [Progress] // an array of progress for each day
init(_ t:String, _ n:String, _ iLT: Bool, _ p: [Progress]) {
id = UUID()
type = t
name = n
isLongTerm = iLT
progress = p
}
}
struct ContentView: View {
#State var goals: [Goal] = []
var body: some View {
TabView{
VStack{
Text("You have")
Text("\(goals.count)")
Text("tasks to do")
}.tabItem { Text("Home")}
MyScroll(numRange: 1..<100, goals: $goals).tabItem { Text("My Goals") }
}
}
}
struct MyScroll: View {
var numRange: Range<Int>
#Binding var goals: [Goal]
var body: some View {
NavigationView{
VStack{
NavigationLink(destination: AddView(goals:$goals)){
Image(systemName: "folder.badge.plus")
}
List(goals) { goal in
HStack(alignment: .center){
Text(goal.name)
}
}
}
}.navigationTitle(Text("1111"))
}
}
struct AddView: View {
#Binding var goals:[Goal]
#State var types = ["study", "workout", "hobby", "habbit"]
#State private var selected = false
#State var selection = Set<String>()
#State var goalName: String = ""
#State var goalType: String = ""
#State var isLongTerm: Bool = false
#State var progress: [Progress] = []
var body: some View {
VStack{
Text("Create your goal")
// type in name
HStack{
TextField("Name", text: $goalName)
}.padding()
// choose type: a selection list
HStack{
List(types, id: \.self, selection: $selection) {
Text($0)
}
.navigationBarItems(trailing: EditButton())
}.padding()
// toggle if it is a logn term goal
HStack{
Toggle(isOn: $selected) {
Text("Is your goal Long Term (no end date)")
}.padding()
}.padding()
Button(action: {
addGoal(goalType, goalName, isLongTerm, progress)
}, label: {
/*#START_MENU_TOKEN#*/Text("Button")/*#END_MENU_TOKEN#*/
})
}
}
// function that add the goal instance to the goals
func addGoal( _ t:String, _ n:String, _ iLT: Bool, _ p: [Progress]){
let item: Goal = Goal(t,n,iLT,[])
goals.append(item)
}
}
Your addGoal function no longer has to be mutating, since it's not actually mutating its own state any more (which doesn't work in SwiftUI anyway).
As a side note, I'd be cautious about writing your initializers and functions like you're doing with the _ unnamed parameters -- I found one in your original code where you meant to be passing the name of the goal but instead were passing the type for that parameter, and because all of the parameters were/are unnamed, there's no warning about it.

Related

How to clear variable when triggered in another view

I am attempting to clear variable values when a button is pressed in another view. Code below:
Root view
struct RecipeEditorHomeMenu: View {
#Environment(\.dismiss) var dismiss
#State var recipeAddedSuccess = false
#State private var showSaveButton = false
#State var showSuccessMessage = false
#State private var sheetMode: SheetMode = .none
#ObservedObject var recipeClass = Recipe()
var onDismiss: (() -> Void)?
var body: some View {
GeometryReader{ geo in
VStack{
if showSuccessMessage {
RecipeSuccessPopUp(shown: $showSuccessMessage, onDismiss: onDismiss)
}
RecipeEditorView(showSuccessMessage: $showSuccessMessage)
.blur(radius: showSuccessMessage ? 15 : 0)
.padding(.top, 80)
// Spacer()
//display save button
FlexibleSheet(sheetMode: $sheetMode) {
SaveRecipeButton(showSuccessMessage: $showSuccessMessage)
//sets coordinates of view on dash
.offset(y:-200)
}
}
.onAppear{
recipeAddedSuccess = false
}
//center view
.alignmentGuide(VerticalAlignment.center, computeValue: { $0[.bottom] })
.position(x: geo.size.width / 2, y: geo.size.height / 2)
.environmentObject(recipeClass)
}
}
}
EditorView (where I would like to clear variables and refresh the view so now it appears new and no variables have values anymore:
struct RecipeEditorView: View {
#EnvironmentObject var recipeClass: Recipe
#State var recipeTitle = ""
#State private var recipeTime = "Cook Time"
#State private var pickerTime: String = ""
//calls macro pickers#State var proteinPicker: Int = 0
#Binding var showSuccessMessage: Bool
var cookingTime = ["5 Mins", "10 Mins","15 Mins","20 Mins","25 Mins","30 Mins ","45 Mins ","1 Hour","2 Hours", "A Long Time", "A Very Long Time"]
var resetPickerTime: (() -> Void)?
var body: some View {
VStack{
TextField("Recipe Title", text: $recipeTitle)
.onChange(of: recipeTitle, perform: { _ in
recipeClass.recipeTitle = recipeTitle
})
.foregroundColor(Color.black)
.font(.title3)
.multilineTextAlignment(.center)
//macro selectors
HStack{
Picker(selection: $carbPicker, label: Text("")) {
ForEach(pickerGramCounter(), id: \.self) {
Text(String($0) + "g Carbs")
}
.onChange(of: carbPicker, perform: { _ in
recipeClass.recipeCarbMacro = carbPicker
})
}
.accentColor(.gray)
}
.accentColor(.gray)
}
HStack(spacing: 0){
ZStack{
Image(systemName:("clock"))
.padding(.leading, 150)
.foregroundColor(Color("completeGreen"))
Picker(selection: $pickerTime, label: Text("Gender")) {
ForEach(cookingTime, id: \.self) {
Text($0)
}
}
.onChange(of: pickerTime, perform: { _ in
recipeClass.recipePrepTime = pickerTime
})
.accentColor(.gray)
}
.multilineTextAlignment(.center)
}
}
}
}
Button where I save the recipes (and would like to clear the variables here:
struct SaveRecipeButton: View {
#Binding var showSuccessMessage: Bool
//stores recipe
#EnvironmentObject var recipeClass: Recipe
#State var isNewRecipeValid = false
static var newRecipeCreated = false
var body: some View {
Button(action: {
//action
}){
HStack{
Image(systemName: "pencil").resizable()
.frame(width:40, height:40)
.foregroundColor(.white)
Button(action: {
if (recipeClass.recipeTitle != "" &&
recipeClass.recipePrepTime != "" &&
){
isNewRecipeValid = true
showSuccessMessage = true
saveRecipe()
}
else{
showSuccessMessage = false
isNewRecipeValid = false
}
}){
Text("Save Recipe")
.font(.title)
.frame(width:200)
.foregroundColor(.white)
}
}
.padding(EdgeInsets(top: 12, leading: 100, bottom: 12, trailing: 100))
.background(
RoundedRectangle(cornerRadius: 10)
.fill(
Color("completeGreen")))
}
.transition(.sideSlide)
.animation(.easeInOut(duration: 0.25))
}
}
My recipe class holds each variable as #Published variables. I tried clearing the variables in this class as well, but the values don't refresh in the view (example: If I have a recipeTitle as "Eggs" and save that recipe down, recipeTitle does not update in the view)
**update recipe class:
class Recipe: ObservableObject, Identifiable {
let id = UUID().uuidString
#Published var recipeTitle: String = ""
#Published var isCompleted = false
#Published var recipeCaloriesMacro: Int = 0
#Published var recipeFatMacro: Int = 0
#Published var recipeProteinMacro: Int = 0
}
I asume you want to reset the values in Recipe. The reason because this doesn´t reflect into RecipeEditorView are the #State values in your RecipeEditorView.
You are neglecting a basic principle of SwiftUI: "Single source of truth".
RecipeEditorView has several #State values that you then bind to your Pickers and TextFields. Then you observe their changes and synchronize with your ViewModel Recipe.
Don´t do that, use the ViewModel itself as the (I know I´m repeating myself here) "Single source of truth".
Another issue. If a view manages the lifecycle of an ObservableObject like instantiating it and holding on to it, declare it as #StateObject. #ObservedObject is only needed it the class is injected and managed up the hierarchy.
In RecipeEditorHomeMenu:
#StateObject var recipeClass = Recipe()
in RecipeEditorView remove all that #State vars that are used to define the recipe e.g.: recipeTitle, carbPicker and pickerTime and bind to the values in the ViewModel.
Possible implementation for recipeTitle:
// this needs to be changed
TextField("Recipe Title", text: $recipeClass.recipeTitle)
Do this for the others too.
Now in SaveRecipeButton you can change these vars to whatever value you want. E.g.: resetting the title would look like:
recipeClass.recipeTitle = ""

Index out of range when updating a #Published var

I am new to Swift/SwiftUI and am trying to build an app that works with the trello API.
There is a "TrelloApi" class that is available as an #EnvironmentObject in the entire app. The same class is also used to make API calls.
One board is viewed at a time. A board has many lists and each list has many cards.
Now I have an issue with my rendering where whenever I switch boards and any list in the new board has fewer cards in it than before, I get the following error in an onReceive handler where I need to do some checks to update the cards appearance:
Swift/ContiguousArrayBuffer.swift:575: Fatal error: Index out of range
2022-10-19 09:04:11.319982+0200 trello[97617:17713580] Swift/ContiguousArrayBuffer.swift:575: Fatal error: Index out of range
Models
struct BoardPrefs: Codable {
var backgroundImage: String? = "";
}
struct BasicBoard: Identifiable, Codable {
var id: String;
var name: String;
var prefs: BoardPrefs;
}
struct Board: Identifiable, Codable {
var id: String;
var name: String;
var prefs: BoardPrefs;
var lists: [List] = [];
var cards: [Card] = [];
var labels: [Label] = [];
}
struct List: Identifiable, Codable, Hashable {
var id: String;
var name: String;
var cards: [Card] = [];
private enum CodingKeys: String, CodingKey {
case id
case name
}
}
struct Card: Identifiable, Codable, Hashable {
var id: String;
var idList: String = "";
var labels: [Label] = [];
var idLabels: [String] = [];
var name: String;
var desc: String = "";
var due: String?;
var dueComplete: Bool = false;
}
TrelloApi.swift (HTTP call removed for simplicity)
class TrelloApi: ObservableObject {
let key: String;
let token: String;
#Published var board: Board;
#Published var boards: [BasicBoard];
init(key: String, token: String) {
self.key = key
self.token = token
self.board = Board(id: "", name: "", prefs: BoardPrefs())
self.boards = []
}
func getBoard(id: String, completion: #escaping (Board) -> Void = { board in }) {
if id == "board-1" {
self.board = Board(id: "board-1", name: "board-1", prefs: BoardPrefs(), lists: [
List(id: "board-1-list-1", name: "board-1-list-1", cards: [
Card(id: "b1-l1-card1", name: "b1-l1-card1"),
]),
List(id: "board-1-list-2", name: "board-1-list-2", cards: [
Card(id: "b1-l2-card1", name: "b1-l2-card1"),
Card(id: "b1-l2-card2", name: "b1-l2-card2"),
])
])
completion(self.board)
} else {
self.board = Board(id: "board-2", name: "board-2", prefs: BoardPrefs(), lists: [
List(id: "board-2-list-1", name: "board-2-list-1", cards: [
]),
List(id: "board-2-list-2", name: "board-2-list-2", cards: [
Card(id: "b2-l2-card1", name: "b2-l2-card1"),
])
])
completion(self.board)
}
}
}
ContentView.swift
struct ContentView: View {
#EnvironmentObject var trelloApi: TrelloApi;
var body: some View {
HStack {
VStack {
Text("Switch Board")
Button(action: {
trelloApi.getBoard(id: "board-1")
}) {
Text("board 1")
}
Button(action: {
trelloApi.getBoard(id: "board-2")
}) {
Text("board 2")
}
}
VStack {
ScrollView([.horizontal]) {
ScrollView([.vertical]) {
VStack(){
HStack(alignment: .top) {
ForEach($trelloApi.board.lists) { list in
TrelloListView(list: list)
.fixedSize(horizontal: false, vertical: true)
}
}
.padding()
.frame(maxHeight: .infinity, alignment: .top)
}
}
}
}
}.onAppear {
trelloApi.getBoard(id: "board-1")
}
.frame(minWidth: 900, minHeight: 600, alignment: .top)
}
}
TrelloListView.swift
struct TrelloListView: View {
#EnvironmentObject var trelloApi: TrelloApi;
#Binding var list: List;
var body: some View {
VStack() {
Text(self.list.name)
Divider()
SwiftUI.List(self.$list.cards, id: \.id) { card in
CardView(card: card)
}
.listStyle(.plain)
.frame(minHeight: 200)
}
.padding(4)
.cornerRadius(8)
.frame(minWidth: 200)
}
}
CardView.swift
struct CardView: View {
#EnvironmentObject var trelloApi: TrelloApi;
#Binding var card: Card;
var body: some View {
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading, spacing: 0) {
Text(card.name)
.bold()
.font(.system(size: 14))
.multilineTextAlignment(.leading)
.lineLimit(1)
.foregroundColor(.white)
Text(card.desc)
.lineLimit(1)
.foregroundColor(.secondary)
}.padding()
Spacer()
}
}
.frame(alignment: .leading)
.onReceive(Just(card)) { newCard in
// CRASH LOCATION: "Index out of range" for self.card.labels
if self.card.labels != newCard.labels {
print("(check if card color should change based on labels)")
}
}
.cornerRadius(4)
}
}
I've highlighted the crash location with a comment. I don't pass any indexes in the ForEach or List and I am overwriting the entire trelloApi.board object, so I am not sure why I am getting this error.
I've tried using ForEach inside of the SwiftUI.List instead, but that also doesn't change anything.
The minimal reproducible code can also be found on my GitHub repo: https://github.com/Rukenshia/trello/tree/troubleshooting-board-switch-crash/trello
The exact issue is hard to track down, but here are some observations and recommandations.
The .onReceive() modifier you are using looks suspicious because you initialize the publisher yourself inline in the function call. You generally use .onReceive() to react to events published from publishers set up by another piece of code.
Moreover, you are using this .onReceive() to react to changes in a #Binding property, which is redundant since by definition a #Binding already triggers view updates when its value changes.
EDIT
This seems to be the issue that causes the crash in your app. Changing the .onReceive() to .onChange() seems to solve the problem:
.onChange(of: card) { newCard in
if self.card.labels != newCard.labels {
print("(check if card color should change based on labels)")
}
}
You also seem to duplicate some state:
.onReceive(Just(card)) { newCard in
self.due = newCard.dueDate
}
Here, you duplicated the due date, there is one copy in self.due and another copy in self.card.dueDate. In SwiftUI there should only be one source of truth and for you it would be the card property. You duplicated the state in the init: self.due = card.wrappedValue.dueDate. Accessing the .wrappedValue of a #Binding/State is a code smell and the sign that you are doing something wrong.
Lastly, ou use an anti-pattern which can be dangerous:
struct CardView: View {
#State private var isHovering: Bool
func init(isHovering: String) {
self._isHovering = State(initialValue: false)
}
var body: some View {
...
}
}
You should avoid initializing a #State property wrapper yourself in the view's init. A #State property must be initililized inline:
struct CardView: View {
#State private var isHovering: Bool = false
var body: some View {
...
}
}
If for some reason you have to customize the value of a #State property, you could use the .onAppear() or the newer .task() view modifier to change its value after the view creation:
struct CardView: View {
#State private var isHovering: Bool = false
var body: some View {
SomeView()
.onAppear {
isHovering = Bool.random()
}
}
}
As a general advice you should break up your views into smaller pieces. When a view depends on many #State properties and has lots of .onChange() or .onReceive() it is usually an indication that it is time to move the whole logic inside and ObservableObject or refactor into smaller components.

New to swift not sure why this isn't working? [duplicate]

I'm attempting to call a function in a struct (swiftui view) which appends an item to an array that is then mapped to a list. The function is being called in a subview, but I keep getting the error "Cannot use mutating member on immutable value: 'self' is immutable".
Heres the function in the parent:
mutating func addNote(note: String){
var newNotes = notes;
newNotes.append(note);
notes = newNotes;
}
Inside the body has:
List {
ForEach(notes, id: \.self) { string in
Section(header: Text("1/22/20")){
Text(string)
}
}...
To pass the function to the subview i try this:
NavigationLink(destination: AddNoteView(delegate: addNote)) {
Text("Add note")
}
and my addNoteView() looks like this:
struct AddNoteView : View {
#State var note: String = ""
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var delegate: (String) -> ()
var body: some View {
NavigationView{
Form{
Section(header: Text("Note")){
TextField("Note", text:$note)
}
Section(){
Button(action: {
delegate(note)
presentationMode.wrappedValue.dismiss()}){
Text("Add note")
}
Anyone having any clue what I'm doing wrong?
SwiftUI view is struct you cannot mutate it from within self. Make notes as #State then you can use
#State var notes: [String] = []
// .. other code
func addNote(note: String) {
notes.append(note)
}

SwiftUI SceneDelegate - contentView Missing argument for parameter 'index' in call

I am trying to create a list using ForEach and NavigationLink of an array of data.
I believe my code (see the end of the post) is correct but my build fails due to
"Missing argument for parameter 'index' in call" and takes me to SceneDelegate.swift a place I haven't had to venture before.
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
I can get the code to run if I amend to;
let contentView = ContentView(habits: HabitsList(), index: 1)
but then all my links hold the same data, which makes sense since I am naming the index position.
I have tried, index: self.index (which is what I am using in my NavigationLink) and get a different error message - Cannot convert value of type '(Any) -> Int' to expected argument type 'Int'
Below are snippets of my code for reference;
struct HabitItem: Identifiable, Codable {
let id = UUID()
let name: String
let description: String
let amount: Int
}
class HabitsList: ObservableObject {
#Published var items = [HabitItem]()
}
struct ContentView: View {
#ObservedObject var habits = HabitsList()
#State private var showingAddHabit = false
var index: Int
var body: some View {
NavigationView {
List {
ForEach(habits.items) { item in
NavigationLink(destination: HabitDetail(habits: self.habits, index: self.index)) {
HStack {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.description)
}
}
}
}
}
}
}
}
struct HabitDetail: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var habits: HabitsList
var index: Int
var body: some View {
NavigationView {
Form {
Text(self.habits.items[index].name)
}
}
}
}
You probably don't need to pass the whole ObservedObject to the HabitDetail.
Passing just a HabitItem should be enough:
struct HabitDetail: View {
#Environment(\.presentationMode) var presentationMode
let item: HabitItem
var body: some View {
// remove `NavigationView` form the detail view
Form {
Text(item.name)
}
}
}
Then you can modify your ContentView:
struct ContentView: View {
#ObservedObject var habits = HabitsList()
#State private var showingAddHabit = false
var body: some View {
NavigationView {
List {
// for every item in habits create a `linkView`
ForEach(habits.items, id:\.id) { item in
self.linkView(item: item)
}
}
}
}
// extract to another function for clarity
func linkView(item: HabitItem) -> some View {
// pass just a `HabitItem` to the `HabitDetail`
NavigationLink(destination: HabitDetail(item: item)) {
HStack {
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.description)
}
}
}
}
}

List selection as Set<String> - how to use?

Am playing around with SwiftUI and am obviously not getting it.
Basic example which works and is just displaying the selected name.
struct ContentView: View {
let names = ["Joe", "Jim", "Paul"]
#State var selectedName = Set<String>()
var body: some View {
VStack {
List(names, id: \.self, selection: $selectedName) { name in
Text(name)
}
if !selectedName.isEmpty {
Text(selectedName.first!) // <-- this line
}
}
}
}
What I want is a textfield where that name can be changed. Tried many ways but getting another error every time.
TextField("Name", text: $selectedName)
Gives this error: Cannot convert value of type 'Binding<Set<String>>' to expected argument type 'Binding<String>'
TextField("Name", text: $selectedName.first!)
Cannot force unwrap value of non-optional type 'Binding<((String) throws -> Bool) throws -> String?>'
How would I do this?
You may make a binding by yourself:
TextField("Name", text: Binding<String>(get: {self.selectedName.first!}, set: { _ in}) )
Obviously you can't pass Binding<Set<String>> to Binding<String>. Here gives you an idea or solution to change selectedName variable using TextField:
I added a new variable which is Binding<String>. Then I change the selectedName inside the TextField's onCommit closure.
struct ContentView: View {
let names = ["Joe", "Jim", "Paul"]
#State var selectedName = Set<String>()
#State var textFieldName = ""
var body: some View {
VStack {
List(names, id: \.self, selection: $selectedName) { name in
Text(name)
}
if !selectedName.isEmpty {
Text(selectedName.first!)
}
Text(textFieldName)
TextField("Name", text: $textFieldName, onEditingChanged: { (Bool) in
//onEditing
}) {
//onCommit
self.selectedName.insert(self.textFieldName)
}
}
}
}
Ok, here is my alternate if I'd needed to edit some value of names having in one screen and list and edit field and make them all synchronised and not confuse each other.
Here is full testable module (tested on Xcode 11.2/iOS 13.2). As I tested it for iOS there are API requirement for put List into EditMode to process selection, so this included.
struct TestChangeSelectedItem: View {
#State var names = ["Joe", "Jim", "Paul"] // made modifiable
#State var selectedName: String? = nil // only one can be edited, so single selection
#State var editMode: EditMode = .active // Tested for iOS, so it is needed
var body: some View {
VStack {
List(selection: $selectedName) {
ForEach(names, id: \.self) { name in
Text(name)
}
}
.environment(\.editMode, $editMode) // Tested for iOS, so it is needed
if selectedName != nil {
Divider()
Text(selectedName!) // Left to see updates for selection
editor(for: selectedName!) // Separated to make more clear
}
}
}
private func editor(for selection: String) -> some View {
let index = names.firstIndex(of: selection)!
var editedValue = selection // local to avoid cycling in refresh
return HStack {
Text("New name:")
TextField("Name", text: Binding<String>(get: { editedValue }, set: { editedValue = $0}), onCommit: {
self.names[index] = editedValue
self.selectedName = editedValue
})
}
}
}
struct TestChangeSelectedItem_Previews: PreviewProvider {
static var previews: some View {
TestChangeSelectedItem()
}
}