I want to call different action sheets in a view, using a variable. That does not mean to work.
.actionSheet(isPresented: self.$neuinitialisierung) {
ActionSheet(
title: Text("Testtitel 1"),
message: Text("Testmessage 1"),
buttons: [
.default(Text("Button 1"),
action: {
print("KLICK")
}),
.default(Text("Button 2"),
action: {
self.clouddienst_waehlen = true;
})
])
}
.actionSheet(isPresented: self.$clouddienst_waehlen) {
ActionSheet(
title: Text("Testtitel 2"),
message: Text("Testmessage 2"),
buttons: [
.default(Text("Button 1"),
action: {
print("KLICK")
}),
.default(Text("Button 2"),
action: {
self.clouddienst_waehlen = true;
})
])
}
If I try it with just one action sheet, it works. How can I use the second?
No need for any of these complicated solutions. Just attach your .actionSheets to different views. It doesn’t need to be on the root level. You can use the .actionSheet modifier on a button for example.
I came up with this solution:
#State var showingMenu = false
#State var optionsMenu: OptionsMenu = .main
enum OptionsMenu { case main, export }
...
.actionSheet(isPresented: $showingMenu) {
if self.optionsMenu == .main {
return ActionSheet(title: Text("Main Menu"), buttons: [
.default(Text("Export Menu")) {
delay(0.1) {
self.optionsMenu = .export
self.showingMenu = true
}
}
.destructive(Text("Close"))
])
} else {
return ActionSheet(title: Text("Export Menu"), buttons: [
.default(Text("Export timeline as GPX")) {
// TODO
},
.default(Text("Export timeline as JSON")) {
// TODO
},
.destructive(Text("Close"))
])
}
}
And the button that opens the first menu needs to make sure to reset the enum value, otherwise the wrong menu will open on next button tap:
Button(action: {
self.optionsMenu = .main
self.showingMenu = true
}) {
Image(systemName: "ellipsis")
}
You can use:
func actionSheet<T>(item: Binding<T?>, content: (T) -> ActionSheet) -> some View where T : Identifiable
It takes a Binding to some kind of optional and if the value is not nil presents an ActionSheet. Instead of setting your a flag to true, you would set this optional to some value.
Related
I have an app where I have several buttons whose actions are shielded by a confirmation dialog. For example:
#State private var confirmDeleteAll: Bool = false
var body: some View {
// ...
Button {
confirmDeleteAll = true
} label: {
Label("Delete all", systemImage: "trash")
}
.confirmationDialog("Delete all data", isPresented: $confirmDeleteAll) {
Button("Delete all", role: .destructive, action: deleteAll)
} message: {
Text("This will wipe all data in the app")
}
// ...
}
These all work fine, and the button in the confirmation dialog shows up in red as expected (seen here on an iPad):
But as I have a load of these, I'd like to refactor the code to make the pattern a little simpler. My principle in the refactor is:
Create a button with the ultimate action to take defined in the button's action argument.
Apply a custom button style which replaces the button's action with displaying a confirmation dialog - inside which is a destructive button that, when pressed, performs the supplied action
That gives me code that looks like
struct ConfirmationButtonStyle: PrimitiveButtonStyle {
var title: LocalizedStringKey
var message: LocalizedStringKey
init(_ title: LocalizedStringKey,
message: LocalizedStringKey = "") {
self.title = title
self.message = message
}
#State private var showConfirmationDialog: Bool = false
func makeBody(configuration: Configuration) -> some View {
Button(role: configuration.role) {
showConfirmationDialog = true
} label: {
configuration.label
}
.confirmationDialog(title,
isPresented: $showConfirmationDialog) {
Button(role: .destructive, action: configuration.trigger) {
configuration.label
}
} message: {
Text(message)
}
}
}
extension PrimitiveButtonStyle where Self == ConfirmationButtonStyle {
static func confirm(
_ title: LocalizedStringKey,
message: LocalizedStringKey
) -> some PrimitiveButtonStyle {
ConfirmationButtonStyle(title, message: message)
}
}
My refactored button then becomes:
Button(action: deleteAll) {
Label("Delete all", systemImage: "trash")
}
.buttonStyle(.confirm(
"Delete all data",
message: "This will wipe all data in the app"
))
From a functional standpoint this works fine. Visually, though, despite the confirmationDialog's button clearly being marked as destructive, its colour reverts to my application's accentColor, which in this case is purple:
From a code point of view I can't see why the destructive role would not be observed. Am I missing something basic here?
I found interesting action in my program:
struct Test: View {
#State private var redButton: Bool = false
var body: some View {
List {
ForEach(1...10, id: \.self) { numbers in
Button {
redButton = false
} label: {
Text("Button \(numbers)")
}.contextMenu {
Button {
//action code
redButton = true
} label: {
Text("Deactivate")
}.disabled(redButton)
}
}
}
}
}
If u run this code and press "Deactivate" in contexMenu, contextMenu will be disabled only for 6..10 buttons, this code switching off/on contextMenu element randomly (try increase or decrease Lists elements and press "Deactivate" on random List element).
If U remove List all working correctly with one Button.
Maybe I need work with dispatchQueue.main.async when change redButton status?
What I doing wrong?
Correct code:
struct Test: View {
#State var redButton: Bool = false
var body: some View {
List {
ForEach(1...3, id: \.self) { numbers in
Menu("Actions \(numbers)") {
Button("Deactivate", action: {
redButton = true
})
Button("Activate", action: {
redButton = false
})
Button(action: {}) {
Label("Delete", systemImage: "trash")
}.disabled(redButton)
Button(action: {}) {
Label("Call", systemImage: "phone")
}.disabled(redButton)
}
}
}
}
}
I have button1, showing sheet1 when pressed. Then I have a toggle, showing a button2, which in turn presents sheet2 when pressed.
If I just press button1, the sheet shows and dismisses with animation. If, however, I press the toggle, which shows button2, and then press button1, the sheet dismiss animation is broken (simply doesn't animate).
struct ContentView: View {
#State private var showSheetButton2 = false
#State private var showSheet1 = false
#State private var showSheet2 = false
var body: some View {
VStack {
Toggle("Show Sheet Button 2", isOn: $showSheetButton2)
Button(action: {
showSheet1.toggle()
}, label: {
Text("Show Sheet 1")
})
.sheet(isPresented: $showSheet1, content: {
Button(action: {
showSheet1 = false
}, label: {
Text("Dismiss")
})
})
if showSheetButton2 {
Button(action: {
showSheet2.toggle()
}, label: {
Text("Show Sheet 2")
})
.sheet(isPresented: $showSheet2, content: {
Button(action: {
showSheet2 = false
}, label: {
Text("Dismiss")
})
})
}
}.padding()
}
}
EDIT:
Fixed in iOS 14.5:
You can now apply multiple sheet(isPresented:onDismiss:content:) and
fullScreenCover(item:onDismiss:content:) modifiers in the same view
hierarchy. (74246633)
Somehow this sheet inside If statement is destroying animation. Please try to add EmptyView outside this if statement and assign sheet to that view.
if showSheetButton2 {
Button(action: {
showSheet2.toggle()
}, label: {
Text("Show Sheet 2")
})
}
EmptyView()
.sheet(isPresented: $showSheet2, content: {
Button(action: {
showSheet2 = false
}, label: {
Text("Dismiss")
})
})
I am trying to open a view on a button click rather than on clicking a Navigation Link, but all I can find on the internet is how to do it with a Navigation Link. So how do you do it without?
Code:
#State var goToMainView = false
NavigationLink(destination: ContentView(),isActive: $goToMainView,label:{}).hidden()
Button(action: {
goToMainView = true
context.delete(goal)
do {
try context.save()
} catch {
print(error)
}
}, label: {
Text("Delete Goal")
})
You can navigate with a button by using NavigationLink
#State var goToSecondView = false
NavigationView {
VStack {
NavigationLink(destination: SecondView(),isActive: $goToSecondView,
label: { EmptyView() })
}.hidden()
Button(action : { goToSecondView = true } , label : { Text("Click On Me") })
}
}
And for more details , I wrote this article , it may help you
How to navigate with SwiftUI
I have a form that saves a new customer when a trailing navigation bar button labeled "Save" is pressed. I would then like to load a new view based on the new customer object. One possible way might be to activate a navigation link with an empty view like this:
NavigationLink(destination: CustomerDetailView(customer: currentCustomer), isActive: self.$showCustomerDetail) { EmptyView() }
Setting showCustomerDetail to true would make trigger the link. But to get a value for currentCustomer I'd need a dynamic fetch request, probably using the new UUID as a predicate. And that's where things fall apart. I can't figure out how to get the result of the fetch request to the currentCustomer variable.
Here are the useful parts of the code:
Navigation Bar Buttons
.navigationBarItems(leading:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Cancel")
}), trailing:
Button(action: {
let newCustomer = Customer(context: self.moc)
newCustomer.id = UUID()
newCustomer.custName = self.name
}
self.appDelegate.saveContext()
self.showCustomerDetail = true
})
}, label: {
Text("Save")
})
Fetch Request
currentCustomer = FetchRequest<Customer>(entity: Customer.entity(), sortDescriptors: [], predicate: NSPredicate(format: "id == %#", custID as CVarArg))
Here is appropriate approach (as newCustomer is already saved into database it can be just used):
.navigationBarItems(leading:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Cancel")
}), trailing:
Button(action: {
let newCustomer = Customer(context: self.moc)
newCustomer.id = UUID()
newCustomer.custName = self.name
}
self.appDelegate.saveContext()
self.currentCustomer = newCustomer // << here !!
self.showCustomerDetail = true
})
}, label: {
Text("Save")
})