How to reload ForEach loop items after sheet is dismissed
Check out your console, you'll see following warning:
ForEach<Range, Int, ModifiedContent<Text, AddGestureModifier<_EndedGesture>>> count (2) != its initial count (1).
ForEach(_:content:)
should only be used for constant data. Instead conform data toIdentifiable
or useForEach(_:id:content:)
and provide an explicitid
!
You could replace 0..<viewModel.players.count
with viewModel.players.indices
to fix it
But if you do, you'll face a crash, because you're initializing selectedItem
in the init, and when new item gets added it doesn't have enough items
You can add new false
to this array somehow, but if you'd like to add new item in the begging, you'll have more problems with up-to-dating your selectedItem
flags array
Instead of that I suggest you adding a unique identifier to your Player
item and storing Set
of this ids for selected player
struct PlayersView: View {
@ObservedObject var viewModel: PlayersViewModel
@State var isSelected: Bool = false
@State private var selectedIds = Set<String>()
@State var selectedPlayers = [String]()
init(viewModel: PlayersViewModel) {
self.viewModel = viewModel
}
var body: some View {
VStack {
ForEach(viewModel.players) { player in
let selected = selectedIds.contains(player.id)
Text("\(String(describing: selected)) \(player.playerName)")
.onTapGesture {
if selected {
selectedIds.remove(player.id)
} else {
selectedIds.insert(player.id)
}
selectedPlayers = viewModel.players
.filter { selectedIds.contains($0.id) }
.map { $0.playerName }
}
}
}
}
}
struct Player: Codable, Identifiable, Hashable {
let id: String
let playerName: String
}
class PlayersViewModel: ObservableObject {
@Published var players = [Player]()
init() {
readJSON()
}
func saveJSON(username: String) {
var array = players
array.append(Player(id: UUID().uuidString, playerName: username))
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("example2.json")
try JSONEncoder().encode(array)
.write(to: fileURL)
readJSON()
} catch {
print(error.localizedDescription)
}
}
func readJSON() {
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("example2.json")
let data = try Data(contentsOf: fileURL)
let players = try JSONDecoder().decode([Player].self, from: data)
self.players = players
print(players)
} catch {
print(error.localizedDescription)
}
}
}
Refresh observableArray when items are not observables
Yes, you can call valueHasMutated
function for your array:
yourArray.valueHasMutated();
EDIT:
If first didn't help you can do 'dirty' refresh:
self.refresh = function(){
var data = self.array().slice(0);
self.array([]);
self.array(data);
};
Here is working fiddle: http://jsfiddle.net/vyshniakov/FuEy6/2/
How do I display an Array of Arrays in swiftUI
you could just do this:
struct ShoppingListView: View {
var shoppingList: [[String]] = [["1","2","3"], ["4","5","6"], ["7","8"]]
var body: some View {
VStack(alignment: .leading) {
Text("number of arrays is \(shoppingList.count)")
ForEach(shoppingList, id: \.self) { list in
VStack {
List(list, id: \.self) { item in
Text("item = \(item)")
}
}
}
}
}
}
SwiftUI using ForEach and onTapGesture to update selected element causing crash
Your problem is likely due to the view diffing algorithm having a different outcome if you have the text value present, since that would be a difference in the root view. Since you have two pieces of state that represent the sheet, and you are force unwrapping one of them, you're leaving yourself open to danger.
You should get rid of showingSheet
, then use the item binding of sheet instead. Make Cards
conform to Identifiable
, then rewrite the sheet modifier as:
.sheet(item: $selectedCard) { SpecificCardView(card: $0) }
This will also reset your selected card to nil when the sheet is dismissed.
Update row in list view from sheet in SwiftUI
It is actually simpler conceptually to start with the data array in this case. Also, I made your Row
struct identifiable as we need to keep track of it. When working with arrays of data in SwiftUI, you should instinctively conform to Identifiable
. Things get infinitely easier.
You were on the right track with thinking you needed an individualized solution for each row, but not having your data in an array, led you into putting that code into your RowView
which actually complicates things. It is much easier to deal with in a ForEach
as everything you need to identify a particular row is there.
The most complicated part of this NOT having an @State
var to keep track of presenting a sheet. Once you pull the sheet code out of the row, this will cause all of the sheets to try and fire. I replaced the need for that with a computed Binding
in .sheet(isPresented:)
that compares an @State var rowID
to the current row id. If they match, then this is the row we want and the sheet fires. Otherwise, nothing happens. The rowID is simply set in the .swipeAction()
button.
struct ListView: View {
@State var rows: [Row] = [Row(name: "Name1", description: "Description1"),
Row(name: "Name2", description: "Description2"),
Row(name: "Name3", description: "Description3"),
Row(name: "Name4", description: "Description4"),
Row(name: "Name5", description: "Description5"),
Row(name: "Name6", description: "Description6")]
@State var rowID: UUID?
var body: some View {
List {
ForEach($rows) { $row in
RowView(row: row)
.swipeActions(edge: .leading, allowsFullSwipe: false) {
Button(action: {
rowID = row.id
}) {
Label("Edit", systemImage: "rectangle.and.pencil.and.ellipsis")
}
}
.sheet(isPresented: Binding<Bool>(
get: { row.id == rowID },
set: { _ in
rowID = nil
})
) {
EditSheet(rowBeingEdited: $row)
}
}
}
}
}
RowView
now has no logic in it, so I could pull our all of the @State
and @Binding
and turn it into a simple display view:
/// A single row to be displayed within a List with a swipe actoun.
struct RowView: View {
// Every RowView is provided a Row entity to draw it's data from.
let row: Row
var body: some View {
HStack {
Text(row.name)
Text(row.description)
}
}
}
/// Simple entity containing data to be displayed in a row view.
struct Row: Identifiable {
let id = UUID()
var name: String
var description: String
}
Lastly, you asked for some preview hints. I simplified the display of the data by putting it directly in to the ListView
state, but some times you need to have a @State
to supply a binding to preview a view because it would normally get it's data from somewhere else (like your original RowView
. That is actually stupidly difficult to inject from a preview provider where it works in a live preview. In a case like that, I will make what I call an Intermediary View" which is a simple
View` struct where I make whatever state I need to run the previewed view. That struct then calls my preview view and supplies the data. I then call the "Intermediary View" from the preview provider. It is simple and works well.
edit:
Example "Intermediary View"
Let's say your ListView
calls for a @Binding
instead of being @State
:
struct ListViewFromSheet: View {
@Binding var rows: [Row]
...
}
You would then write your "Intermediary View" like this:
struct IntermediaryListView: View {
@State var rows: [Row] = [Row(name: "Name1", description: "Description1"),
Row(name: "Name2", description: "Description2"),
Row(name: "Name3", description: "Description3"),
Row(name: "Name4", description: "Description4"),
Row(name: "Name5", description: "Description5"),
Row(name: "Name6", description: "Description6")]
var body: some View {
ListView(rows: $rows)
}
}
Then your preview provider simply calls the intermediary view:
struct ListViewFromSheet_Previews: PreviewProvider {
static var previews: some View {
IntermediaryListView()
}
}
That example is a bit contrived simply because if you needed data for all of those views, you would probably supply it where all of the views could get it, but it works equally well where you might have a @State
var in the hierarchy that acted as a flag for state in the view and was a @Binding
. Say your view had a button that changed the flag and you needed to test the results of changing that flag in your view. In setting that up in a preview provider, you need to have a @State static var
to set that up. While that is not terribly, things get more complicated from there, and it is easier to embed some code in a regular View struct, and that code can generally be copied directly from the calling View.
Fatal error: index out of range' when deleting bound object in view
ForEach<Range>
is constant range container (pay attention on below description of constructor), it is not allowed to modify it after construction.
extension ForEach where Data == Range<Int>, ID == Int, Content : View {
/// Creates an instance that computes views on demand over a *constant*
/// range.
///
/// This instance only reads the initial value of `data` and so it does not
/// need to identify views across updates.
///
/// To compute views on demand over a dynamic range use
/// `ForEach(_:id:content:)`.
public init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
}
If you want to modify container, you have to use ForEach(activeWorkout.exercises)
Related Topics
Querying Geohashes in Firestore Returns Nothing
Custom Markers Disappear on Zoomin The Map and Appear on Zoomout The Map with Clustering
How to Set a Known Position and Orientation as a Starting Point of Arkit
Closure Identity in Swift: Unregister Observing Closure
Swift: Check Which Value in Nsarray Is Closest to Another Given Value
In Swift, Dynamic Height for UItextview in UIcollectionview
Using Scenekit for Hittesting Not Returning a Hit with Scnnode
Urlcomponents Queryitems Losing Percent Encoding When Mutated
Update UIapplicationshortcutitem from Extension
The "Funk" Sound When Hitting Escape Key in App
Why Does My Swift Bundle Get The Wrong Principal Class
Do Swift Hashable Protocol Hash Functions Need to Return Unique Values
Typecase Regular Swift Function to Curry Function
How to Find Multiple Nsrange for a String from Full String iOS Swift
Why Are Implicitly Unwrapped Variables Now Printing Out as Some(...) in Swift 4.1
How to Get The .Mlmodel to Be Used in Swift Playground