I already found the solution, but still like to understand what the issue was to be able to transfer it to similar problems.
Take this example code:
import SwiftUI
struct ContentView: View {
private var days = Array(1...31)
#State private var selectedDay = 1
private var months = [ "January", "February", "March", "April", "May", "June" ]
#State private var selectedMonth = "January"
var body: some View {
NavigationView {
Form {
VStack {
Picker("Select day", selection: $selectedDay) {
ForEach(self.days, id: \.self) { day in
Text(String(day))
}
}
Picker("Select month", selection: $selectedMonth) {
ForEach(self.months, id: \.self) { month in
Text(month)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you then tap on any of the pickers the application will crash after a few seconds with Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffeed371fd8).
The solution was to remove the VStack.
But I still like to understand why the application crashes if there is a VStack?
What’s wrong about adding a VStack?
Form is actually a List and every View in Form's view builder is put into row, so combining two picker into VStack result it putting two pickers into one row and when you tap on that row which picker list should be shown? ... unknown - thus this is a reason of crash.
If you want to combine such views in form use Section, like
Form {
Section {
Picker("Select day", selection: $selectedDay) {
ForEach(self.days, id: \.self) { day in
Text(String(day))
}
}
Picker("Select month", selection: $selectedMonth) {
ForEach(self.months, id: \.self) { month in
Text(month)
}
}
}
}
Related
I have multiple views created by a ForEACH. Each View has a textfield where a user can enter a number. I would like to subtotal each entry in each view. In other words subtotal the binding in each view.
Is my approach wrong?
ForEach(someArray.allCases, id: \.id) { item in
CustomeRowView(name: item.rawValue)
}
struct CustomeRowView: View {
var name: String
#State private var amount: String = ""
var body: some View {
VStack {
HStack {
Label(name, systemImage: image)
VStack {
TextField("Amount", text: $amount)
.frame(width: UIScreen.main.bounds.width / 7)
}
}
}
}
}
Any help would be greatly appreciated.
there are many ways to achieve what you ask. I present here a very
simple approach, using an ObservableObject to keep the info in one place.
It has a function to add to the info dictionary fruits.
A #StateObject is created in ContentView to keep one single source of truth.
It is passed to the CustomeRowView view using #ObservedObject, and used to tally
the input of the TextField when the return key is pressed (.onSubmit).
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class FruitCake: ObservableObject {
#Published var fruits: [String : Int] = ["apples":0,"oranges":0,"bananas":0]
// adjust for you purpose
func add(to name: String, amount: Int) {
if let k = fruits.keys.first(where: {$0 == name}),
let sum = fruits[k] {
fruits[k] = sum + amount
}
}
}
struct ContentView: View {
#StateObject var fruitCake = FruitCake()
var body: some View {
VStack {
ForEach(Array(fruitCake.fruits.keys), id: \.self) { item in
CustomeRowView(name: item, fruitCake: fruitCake)
}
}
}
}
struct CustomeRowView: View {
let name: String
#ObservedObject var fruitCake: FruitCake
#State private var amount = 0
var body: some View {
HStack {
Label(name, systemImage: "info")
TextField("Amount", value: $amount, format: .number)
.frame(width: UIScreen.main.bounds.width / 7)
.border(.red)
.onSubmit {
fruitCake.add(to: name, amount: amount)
}
// subtotal
Text("\(fruitCake.fruits[name] ?? 0)")
}
}
}
Currently I am trying to update the count in a SingleDay struct inside a Days class from from the TestScreen view.The SingleDay struct is also in an array in Days. The change in count should be reflected in the UpdatingArrayElements view. So far I am running into this error:
Left side of mutating operator isn't mutable: 'day' is a 'let' constant"
and I have absolutely no idea on how to resolve this issue. I would appreciate any help given that I am still a beginner in iOS development and still trying to get the hang of building more complex iOS apps, thanks!
import SwiftUI
struct SingleDay: Identifiable {
let id: String = UUID().uuidString
let day: Int
var count: Int
}
class Days: ObservableObject {
#Published var daysArray: [SingleDay] = []
init() {
daysArray.append(SingleDay(day: 1, count: 0))
}
}
struct UpdatingArrayElements: View {
#StateObject var days: Days = Days()
var body: some View {
NavigationView {
List {
ForEach(days.daysArray) { day in
HStack{
Text("Day: \(day.day)")
Text("Count: \(day.count)")
}
}
}
.navigationBarItems(trailing:
NavigationLink(destination: TestScreen(dayViewModel: days), label: {
Image(systemName: "arrow.right")
.font(.title)
})
)
}
}
}
struct TestScreen: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var dayViewModel: Days
var body: some View {
ZStack {
Color.green.ignoresSafeArea()
VStack {
ForEach(dayViewModel.daysArray) { day in
Text(String(day.day))
Button(action: {
day.count += 1
}, label: {
Text("Add count")
})
}
}
}
}
}
struct UpdatingArrayElements_Previews: PreviewProvider {
static var previews: some View {
UpdatingArrayElements()
}
}
here you go. now we are not using the identifiable so if you want you can also remove it.
so the real problem was that the day which is of type SingleDay is a struct not a class. so foreach give us let not vars , so in case of class object we can easily update properties because they are reference type, but in case of struct we can not do that, so it means day is another copy. so even if we can update the day of struct it will still not update the daysArray element.
struct TestScreen: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var dayViewModel: Days
var body: some View {
ZStack {
Color.green.ignoresSafeArea()
VStack {
ForEach(dayViewModel.daysArray.indices ,id : \.self) { index in
let day = dayViewModel.daysArray[index]
Text("\(day.count)")
Button(action: {
dayViewModel.daysArray[index].count = day.count + 1
print( day.count)
}, label: {
Text("Add count")
})
}
}
}
}
}
I am trying to make a list of selectionable rows in Swift UI, the rows including a Picker. Everything works fine, except that the content of the Picker disappears when selected, see attached screenshot (but is actually visible when the window of the app is not active (i.e. when I click on another window))
I tried everything I could think of, I could not solve this issue. Below a minimal code to reproduce the problem. Anyone has any idea, how to get around this problem
SelectionList.swift
struct SelectionList: View {
#State var dudes: [String] = ["Tim", "Craig", "Phil"]
#State var selectedRow = Set<String>()
var body: some View {
return List(selection: $selectedRow) {
ForEach(self.dudes, id: \.self) { item in
SelectionRow()
}
}
}
}
SelectionRow.swift
struct SelectionRow: View {
#State var selectedFruit = 0
let fruits = ["Apples", "Oranges", "Bananas", "Pears"]
var body: some View {
Picker(selection: self.$selectedFruit, label: EmptyView()) {
ForEach(0 ..< fruits.count, id: \.self) {
Text(self.fruits[$0])
}
}
}
}
Consider the following project with two views. The first view presents the second one:
import SwiftUI
struct ContentView: View {
private let data = 0...1000
#State private var selection: Set<Int> = []
#State private var shouldShowSheet = false
var body: some View {
self.showSheet()
//self.showPush()
}
private func showSheet() -> some View {
Button(action: {
self.shouldShowSheet = true
}, label: {
Text("Selected: \(selection.count) items")
}).sheet(isPresented: self.$shouldShowSheet) {
EditFormView(selection: self.$selection)
}
}
private func showPush() -> some View {
NavigationView {
Button(action: {
self.shouldShowSheet = true
}, label: {
NavigationLink(destination: EditFormView(selection: self.$selection),
isActive: self.$shouldShowSheet,
label: {
Text("Selected: \(selection.count) items")
})
})
}
}
}
struct EditFormView: View {
private let data = 0...1000
#Binding var selection: Set<Int>
#State private var editMode: EditMode = .active
init(selection: Binding<Set<Int>>) {
self._selection = selection
}
var body: some View {
List(selection: self.$selection) {
ForEach(data, id: \.self) { value in
Text("\(value)")
}
}.environment(\.editMode, self.$editMode)
}
}
Steps to reproduce:
Create an app with the above two views
Run the app and present the sheet with the editable list
Select some items at random indexes, for example a handful at index 0-10 and another handful at index 90-100
Close the sheet by swiping down/tapping back button
Open the sheet again
Scroll to indexes 90-100 to view the selection in the reused cells
Expected:
The selected indexes as you had will be in “selected state”
Actual:
The selection you had before is not marked as selected in the UI, even though the binding passed to List contains those indexes.
This occurs both on the “sheet” presentation and the “navigation link” presentation.
If you select an item in the list, the “redraw” causes the original items that were originally not shown as selected to now be shown as selected.
Is there a way around this?
It looks like EditMode bug, worth submitting feedback to Apple. The possible solution is to use custom selection feature.
Here is a demo of approach (modified only part). Tested & worked with Xcode 11.4 / iOS 13.4
struct EditFormView: View {
private let data = 0...1000
#Binding var selection: Set<Int>
init(selection: Binding<Set<Int>>) {
self._selection = selection
}
var body: some View {
List(selection: self.$selection) {
ForEach(data, id: \.self) { value in
self.cell(for: value)
}
}
}
// also below can be separated into standalone view
private func cell(for value: Int) -> some View {
let selected = self.selection.contains(value)
return HStack {
Image(systemName: selected ? "checkmark.circle" : "circle")
.foregroundColor(selected ? Color.blue : nil)
.font(.system(size: 24))
.onTapGesture {
if selected {
self.selection.remove(value)
} else {
self.selection.insert(value)
}
}.padding(.trailing, 8)
Text("\(value)")
}
}
}
After upgrade to Xcode 11 beta 6, DefaultPicker is behaving like a wheelPicker
var colors = ["Mumbai", "Delhi", "Chennai", "Hyderabad"]
#State private var selectedColor = 0
var body: some View {
VStack {
Picker(selection: $selectedColor, label: Text("Please choose a city")) {
ForEach(0 ..< colors.count) {
Text(self.colors[$0])
}.pickerStyle(DefaultPickerStyle())
}
}
}
}
I wanted single list row that navigates into a new list of possible optionsPicker
I found the issue added the picker inside Form, here the final code
#State private var selectedColor = 0
var body: some View {
NavigationView {
Form {
Picker(selection: $selectedColor, label: Text("Please choose a city")) {
ForEach(0 ..< colors.count) {
Text(self.colors[$0])
}.pickerStyle(DefaultPickerStyle())
}
}
}
}
I don't know what you are exactly trying to achieve but I guess you want to use segement picker.
replace
DefaultPickerStyle()
with
SegmentedPickerStyle()