I need some help with a strange behavior in Apple's new Table view (since macOS 12).
Table(diagnoses, sortOrder: $diagnoses.sortDescriptors) {
TableColumn("ICD10", value: \.icd!) { dia in
Text(dia.icd ?? "XXX.X")
}
.width(min: 30, ideal: 40, max: 50)
TableColumn("diagnosis", value: \.diagnosis!) { dia in
Text(dia.diagnosis ?? "notSpec")
}
TableColumn("gestellt", value: \.madeOn!) { dia in
if dia.madeOn != nil {
Text(Formatters.dateFormatter.string(from: dia.madeOn!))
}
}
.width(min: 60, ideal: 70, max: 80)
TableColumn("") { dia in
HStack {
Button(
action: {
print(dia) // <<-- here correct "dia"
showDiaDelAlert.toggle()
},
label: {
Image(systemName: "trash")
}
)
.buttonStyle(.borderless).foregroundColor(Color.accentColor)
Button(
action: {
showingDiaEditScreen.toggle()
},
label: {
Image(systemName: "pencil")
}
)
.buttonStyle(.borderless).foregroundColor(Color.accentColor)
}
.confirmationDialog("alertDelete", isPresented: $showDiaDelAlert) {
Button("ok", role: .destructive){
print(dia) // <<-- here other "dia". Mainly the first item in Table
deleteItem(item: dia, moc: moc)
}
Button("cancel", role: .cancel) {
showDiaDelAlert.toggle()
}.keyboardShortcut(.defaultAction)
}
.sheet(isPresented: $showingDiaEditScreen) {
EditDiagnosisView(diagno: dia)
}
}
.width(min: 30, ideal: 40, max: 50)
}
.tableStyle(.bordered(alternatesRowBackgrounds: true))
Everything works fine so far. But with editing (showing sheet), and deleting there are some problems.
Editing: the form in the sheet doesn't display the right row and when closing the sheet, for every other row die sheet is displayed again.
Deleting: the wrong row is deleted, and the alert appears once again. Confirmation will delete the next / previous row, too.
I need to place buttons, because Table doesn't offer swipe actions (or?). And I need to place the .confirmationDialog and the .sheet somewhere within the TableColumn to get the dia.
Any ideas, what's wrong and how to solve this?
Related
I'm trying to add a simple swipe action to my list. But for some reason the button can not be pressed. When I perform a full swipe it works though.
This is my code:
var listView: some View {
List {
ForEach(Array(debits.enumerated()), id: \.element) { index, debit in
HStack {
Text(debit.name)
.swipeActions(allowsFullSwipe: true) {
Button(action: {
print("Hi \(index)")
}, label: {
Image(systemName: "slider.horizontal.3")
})
}
Spacer()
Text(String(debit.value))
.frame(width: 70, alignment: .trailing)
Text("€")
Divider().padding(.leading, 5)
Toggle("", isOn: $debits[index].toggle)
.onChange(of: debit.toggle) { newValue in
calculateAvailable()
}
.frame(width: 50)
}
}
}.listStyle(.plain).lineLimit(1)
}
Like I said, a press on the button does not print anything but the full swipe does.
Move the .swipeActions modifier to the HStack containing the whole row:
List {
ForEach(Array(debits.enumerated()), id: \.element) { index, debit in
HStack {
Text(debit.name)
Spacer()
Text(String(debit.value))
.frame(width: 70, alignment: .trailing)
Text("€")
Divider().padding(.leading, 5)
Toggle("", isOn: $debits[index].toggle)
.onChange(of: debit.toggle) { newValue in
calculateAvailable()
}
.frame(width: 50)
}
.swipeActions(allowsFullSwipe: true) {
Button(action: {
print("Hi \(index)")
}, label: {
Image(systemName: "slider.horizontal.3")
})
}
}
}
Also, I would recommend adding a stable id to your Debit structure. Something like this:
struct Debit: Hashable, Identifiable {
var name: String
var value: Double
var toggle: Bool
let id = UUID()
}
and use that as your id:
ForEach(Array(debits.enumerated()), id: \.element.id) { index, debit in
If you weren’t enumerating to get the index, then you’d simply be able to iterate over debits since the items are Identifiable:
ForEach($debits) { $debit in
This is working fine, the HStack is your problem. Try to set the swipaction on the HStack
List {
ForEach(YourList, id: \.self) { debit in
Text(debit)
.swipeActions(allowsFullSwipe: true) {
Button(action: {
print("Hi \(index)")
}, label: {
Text("test")
})
}
}
}
enter code here
I just found the answer. I used this code to dismiss the keyboard when tapping somewhere. This prevented the button from being pressed.
var body: some View {
VStack {
my code
}
.onTapGesture {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
I also had this exact same problem and it turns out a containing view had an .onTapGesture handler which was capturing the presses on the button effectively making it only work after the full swipe, but not when tapped.
The following code shows different Pickers.
I want to achieve: MenuPickerStyle with Sections and custom Label like in second example https://imgur.com/a/Q0uxEmh but it is not selectable because it is grayed out
When doing it with the Menu container as recommended in the internet the rows are grayed out. How can I get the wanted behavior?
In the code you can see 4 different examples. The second is the grayed out picker rows.
Tested in Xcode 13.2.1, iOS 15
struct ContentView: View{
#State var number_1 = 0
#State var number_2 = 0
#State var number_3 = 0
#State var number_4 = 0
let numbers = [0,1,2,3,4]
var body: some View{
VStack{
//Picker works, no Section --------------------------
Menu(content: {
Picker(selection: $number_1, content: {
ForEach(numbers,id: \.self){i in
Text("Number: \(i)").tag(i)
}
}, label: {EmptyView()})
}, label: {
Text("selected Number = \(number_1)")
})
//Picker is grayed out ---------------------------
Menu(content: {
Picker(selection: $number_2, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {EmptyView()})
}, label: {
Text("selected Number = \(number_2)")
})
//Picker works, collapsed View not desired behavior --------
Menu(content: {
Picker(selection: $number_3, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {EmptyView()})
.pickerStyle(MenuPickerStyle())
}, label: {
Text("selected Number = \(number_3)")
})
//Picker Works, label not ----------------------------
Picker(selection: $number_4, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {Text("selected Number = \(number_4)")})
.pickerStyle(MenuPickerStyle())
}
}
}
You can put multiple Pickers in a Menu, each with a subset of the available values, and they’ll all work as expected:
Menu {
// First section, with values 0–2
Picker(selection: $num) {
ForEach(0..<3, id: \.self) { i in
Text("Number: \(i)").tag(i)
}
} label: {
EmptyView()
}
// Second section, with values 3–5
Picker(selection: $num) {
ForEach(3..<6, id: \.self) { i in
Text("Number: \(i)").tag(i)
}
} label: {
EmptyView()
}
} label: {
Text("selected Number = \(num)")
}
SwiftUI will even stick a separator between the Pickers automatically.
The simplest fix is for case 4 - just rebuild menu (force-refresh) on selection changed:
//Picker Works, label not ----------------------------
Picker(selection: $number_4, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {Text("selected Number = \(number_4)")})
.pickerStyle(MenuPickerStyle())
.id(number_4) // << here !!
Tested with Xcode 13.4 / iOS 15.5
I think I've got a workaround.
I tried to use a menu and use Buttons for manipulating the selection. The problem is the mark of the selection if there is already a Image in the label of the button I think.
struct ContentView: View{
#State var number = 0
let numbers = [1,2,3,4]
var body: some View{
Menu(content: {
ForEach(numbers,id:\.self){i in
Section{
Button(action: {
number = i
}, label: {
Text("Number: \(i)")
})
}
}
}, label: {
Text("The selection is: \(number)")
.frame(width: UIScreen.main.bounds.width, height: 20)
.border(Color.green)
})
}
}
I'm not 100% certain I understand, but if your goal is to build a picker that lets the user click on the title and then select an option from a menu, returning the selection to the screen I would suggest doing it as follows:
struct SwiftUIView: View {
#State var option:Int = 0
let numbers = [1,2,3,4]
var body: some View {
VStack {
//Picker to select the option
Picker("Option Picker", selection: $option) {
ForEach(numbers, id: \.self) {
number in
Text("Selection Number \(number)").tag(number)
}
}.pickerStyle(MenuPickerStyle())
//Text confirming the selection won't show before a pick is made
Text(option != 0 ? "You selected option number \(option)" : "")
}
}
}
Let me know if that works for you / is what you were looking for.
I want to add a button to swiftui picker's label.
But the button is not clickable.
When I click on the button the picker is clicked.
How Do I make the picker take clicks only in the area of the selected value?
and the buttons take his clicks?
import SwiftUI
enum Animal: String, CaseIterable, Identifiable {
case dog
case cat
case bird
var id: String { self.rawValue }
}
struct ContentView: View {
#State private var selectedAnimal = Animal.dog
var body: some View {
Form {
Group {
Section(header: Text("Animales")) {
VStack{
Picker(
selection: $selectedAnimal,
content: {
ForEach(Animal.allCases, id:\.self) {
Text($0.rawValue)
}},
label: {
HStack {
Text ("Chose Animale")
Spacer ()
Button (
action: {
print ("clicked")
},
label: {
Image(systemName: "arrow.clockwise")
})
Spacer ()
}
}
)
}
}
}
}
}
}
To solve this issue we need to separate picker and button and block Form tracking click inside row (which is by default track entire row).
For first move button out of picker and place everything in HStack, for second we need couple of tricks like tapGesture on label and non-default button style for button (for simplicity I used primitive button style, but it's better to create custom with appropriate highlight, etc.)
Here is a simplified updated and tested your code (Xcode 13 / iOS 15):
var body: some View {
Form {
Group {
Section(header: Text("Animales")) {
HStack{
HStack {
Text ("Chose Animale")
Spacer ()
}
.contentShape(Rectangle())
.onTapGesture {
// just blocker for label click
}
.overlay(
Button (
action: {
print ("clicked")
},
label: {
Image(systemName: "arrow.clockwise").foregroundColor(.blue)
})
.buttonStyle(PlainButtonStyle()) // << needed custom !!
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.layoutPriority(1) // << to cover much area
//.border(Color.red) // << for testing area
Picker("",
selection: $selectedAnimal,
content: {
ForEach(Animal.allCases, id:\.self) {
Text($0.rawValue)
}}
)
.labelsHidden() // << hide own label
.fixedSize() // << limit size !!
}
.listRowInsets(EdgeInsets()) // << consume row space !!
}
}
}
}
I am using swipeActions in ForEach loop of a List and toolbar buttons to show different sheets on my SwiftUI view. But every first time I swipe left and click the Edit button, the object of that line is nil. If I do the same swipe and click thing again, everything goes well. Anyone else had this kind of bug before? Thank you.
Here is the related code:
struct LanguagesView: View {
#State var activeSheet: ActiveSheet?
#State var toBeEdit: MeLanguage?
var body: some View {
NavigationView {
List {
ForEach(self.meLanguages, id: \.id) { lan in
HStack {
Text("\(lan.wrappedName)")
.font(.headline)
}.swipeActions(allowsFullSwipe: false) {
Button(
action: {
self.activeSheet = .editLanguage
self.toBeEdit = lan
},
label: { Label("Edit", systemImage: "pencil") }
) .tint(.indigo)
}
}
}
.sheet(item: $activeSheet,
onDismiss: {
self.toBeEdit = nil
}
){
item in
switch item {
case .addLanguage:
AddLanguage()
case .sortLanguages:
SortLanguagesView()
case .editLanguage:
if self.toBeEdit != nil {
EditLanguageView( meLanguage: self.toBeEdit! )
}
else {
Text("self.toBeEdit is nil")
}
default:
Text("No such button on ContentView.")
}
}
.toolbar {
ToolbarItemGroup {
HStack {
Text("\(self.meLanguages.count) Languages on Card").font(.headline)
self.barButtons
}
}
}
}
}
var barButtons: some View {
HStack {
Button(
action: {
self.activeSheet = .sortLanguages
},
label: { Label("Sort Languages", systemImage: "arrow.up.arrow.down.circle")
}
).id("sortLanguages")
Button(
action: {
self.activeSheet = .addLanguage
},
label: { Label("Add Language",
systemImage: "plus")
.imageScale(.large)
}
)
}
}
}
If I only think of the sheet triggered by swipeActions Edit button, the code below works perfectly. But I still need other sheets triggered by ToolBar buttons.
.sheet(item: self.$toBeEdit, content: { toBeEdit in
EditLanguageView( meLanguage: toBeEdit)
})
After more searching I realised it's not something about SwipeActions. This is actually similar to this question:
SwiftUI presenting sheet with Binding variable doesn't work when first presented
So I added an hidden Text after the ForEach loop and the problem solved.
ForEach(self.meLanguages, id: \.id) { ... }
if self.toBeEdit != nil {
Text("\(self.toBeEdit!.wrappedName)").hidden()
}
I'm trying to animate the hiding/showing of a picker based on a toggle. If true or false, I would like the picker to easeInOut.
I've tried adding the .animation(Animation.easeInOut(duration: 0.5)) to the picker itself or the HStack the picker is in, but both add the animation to values inside the picker and when scrolling through the values the application to crashes.
HStack {
if showPicker {
Picker(selection: $selected.value, label: Text(selected.type)) {
ForEach(digits, id: \.self) { d in
Text("\(d)")
}
}
.frame(width: 40)
}
}
.animation(Animation.easeInOut(duration: 2))
if showPicker {
Picker(selection: $selected.value, label: Text(selected.type)) {
ForEach(digits, id: \.self) { d in
Text("\(d)")
}
}
.frame(width: 40)
.animation(Animation.easeInOut(duration: 0.5))
}
Both options do animate the hiding/showing the picker, but it also animates scrolling through the values in the picker, which causes it to crash.
Any help would be appreciated.
Thank you
About your first approach, putting animation on HStack. Never do that. According to the comments in the declaration file:
Use this modifier on leaf views rather than container views. The
animation applies to all child views within this view; calling
animation(_:) on a container view can lead to unbounded scope.
I tried your second approach (filling the missing bits from your post), and it won't crash. Maybe you can update your question with a fully reproducible example.
Changed animation to explicit, so other parameters are not affected:
struct PickerStackOverflow: View {
#State private var showPicker = true
#State private var value: Int = 1
let digits: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
var body: some View {
VStack {
Text("Selected \(value)")
HStack {
if showPicker {
Picker(selection: $value, label: Text("Label")) {
ForEach(digits, id: \.self) { d in
Text("\(d)")
}
}
.frame(width: 40)
}
}
Button("Tap Me") {
withAnimation(Animation.easeInOut(duration: 2)) {
self.showPicker.toggle()
}
}
}
}
}