is there a way to get a destructive Button style in SwiftUI?
I know I can do this for a ContextMenu, but I did not find a way for "normal" Button.
Cheers
iOS 15
From iOS 15 You can (and you should!) assign a Role to each button like:
Button("Delete", role: .destructive) {
deleteSomething()
}
Assigning the role to a button helps the system to apply the proper style for each context that uses the button (for example like this example for a context menu)
More customization
You can create a combination of modifiers to create the needed style.
Demo
Code for above example:
VStack {
Button("Plain", role: .none, action: { })
.buttonStyle(PlainButtonStyle())
Button("Automatic", role: .none, action: { })
.buttonStyle(.automatic)
Button("Log out", role: .cancel, action: { })
.buttonStyle(BorderedButtonStyle())
.tint(.yellow)
// with controlSize
Button("Cancel", role: .cancel, action: { })
.buttonStyle(.borderless)
.controlSize(.small)
.tint(.yellow)
Button("Delete", role: .destructive, action: { })
.buttonStyle(.bordered)
.controlSize(.regular)
// with controlProminence
Button(role: .destructive, action: { }, label: {
Text("Exit").frame(maxWidth: .infinity)
})
.buttonStyle(.bordered)
.controlSize(.large)
.controlProminence(.increased)
//with BorderedShape
Button(role: .destructive, action: { }, label: {
Text("Wow shape").frame(maxWidth: .infinity)
})
.buttonStyle(BorderedButtonStyle(shape: .capsule))
.controlSize(.large)
.controlProminence(.increased)
.tint(.purple)
}
For Button:
Button("Tap") {
// do something
}
.foregroundColor(.red)
For Alert:
Alert(
title: Text("Hi"),
message: Text("Do it?"),
primaryButton: .cancel(Text("Cancel")),
secondaryButton: .destructive(Text("Delete")) {
// do something
}
)
And similarly for ActionSheet.
Related
My expectation: I want to open a new full screen view (PhoneNumberView) on click of confirmation dialog button.
Reality: Nothing happens when I click to Back button (please check my code below)
Here is my code:
SetPasswordButton(value: "exitButton",
password: $password,
key: $key,
unlocked: $unlocked,
repeatPassword: $repeatPassword, isExitButtonTapped: $isExitButtonTapped)
.confirmationDialog("", isPresented: $isExitButtonTapped) {
Button("Back", role: .destructive) {
goToPhoneScreen = true
}
.fullScreenCover(isPresented: $goToPhoneScreen {
PhoneNumberView()
}
Button("Cancel", role: .cancel) {}
}
Could you help me?
You have misplaced the .fullScreenCover in your code, that should be something like this, i have tested it and it's working as expected, here is the Full code,
struct ContentView: View {
var body: some View {
SetPasswordButton(value: "exitButton",
password: $password,
key: $key,
unlocked: $unlocked,
repeatPassword: $repeatPassword, isExitButtonTapped: $isExitButtonTapped)
.confirmationDialog("", isPresented: $isExitButtonTapped) {
Button("Back") {
goToPhoneScreen = true
}
}
.fullScreenCover(isPresented: $goToPhoneScreen){
PhoneNumberView()
}
Button("Cancel", role: .cancel) {}
}
}
Let's say I have two routes.
One route is a "new Item" route and one is a "view Item" route.
In order to create a blank form I was just inserting a "blank" object into the database and then redirecting to the item view.
I understand this may not be the best way to do this, and I'm open to suggestions because I don't find a lot of tutorials that deal with this paradigm. Most just have a separate input form (which I don't want).
So the problem I'm running into is that when I navigate to my /new route, it ends up duplicating all the template content in the dom for my /org route even though it's just getting redirected to my /org route. I can reload the /org route all day with different "org data" and it never duplicates anything in my DOM. But the instant I hit that /new route button, it will add another set of DOM elements in duplicate fashion.
Can someone help me shed some light on what's going on? Here's my /new route code...
Router.route('/new/', {
name: 'new',
action: function() {
var newId = Organizations.insert({
// Set some defaults
init_date: new Date(),
modified_date: new Date()
});
this.redirect('/org/' + newId);
}
});
Here's my /org route code:
Router.route('/org/:_id', {
name: 'org',
subscriptions: function() {
return [
Meteor.subscribe('contacts', this.params._id),
Meteor.subscribe('organization', this.params._id)
];
},
onBeforeAction: function() {
user = Meteor.user();
if (!Roles.userIsInRole(user, ['admin'])) {
this.redirect('/submit-ticket');
this.stop();
} else {
this.next();
}
},
action: function() {
if (this.ready()) {
Session.set('currentOrgId', this.params._id);
this.layout('appLayout');
this.render('orgHead', {
to: 'orghead',
data: function() {
return Organizations.findOne({
_id: this.params._id
});
}
});
this.render('orgInfo', {
to: 'content',
data: function() {
return Organizations.findOne({
_id: this.params._id
});
}
});
} else {
this.layout('appLayout');
this.render('loading', {
to: 'content'
});
}
}
});
I want to alter how an asyncCommand is being hit (currently from a button), so I would need to access the asyncCommand from code. I don't want to have to alter what this asyncCommand is doing, it is dealing with payment details.
I have tried Googling but I cant find anything, I am also new to KO.
This is what I'm trying to achieve:
Click on a button (a separate button with its own asyncCommand method
which checks a flag) The 'execute' will do the following:
If (flag) - show modal
modal has two options - Continue / Cancel
If continue - hit asyncCommand command for original button (card payment one).
If cancel - go back to form
If (!flag)
Hit asyncCommand command for original button (card payment one).
Can this be done?
Thanks in advance for any help.
Clare
This is what I have tried:
FIRST BUTTON
model.checkAddress = ko.asyncCommand({
execute: function (complete)
{
makePayment.execute();
if (data.shippingOutOfArea === true || (data.shippingOutOfArea === null && data.billingOutOfArea === true)) {
model.OutOfArea.show(true);
}
complete();
},
canExecute: function (isExecuting) {
return !isExecuting;
}
});
ORIGINAL BUTTON
model.makePayment = ko.asyncCommand({
execute: function (complete) {
}})
MODAL
model.OutOfArea = {
header: ko.observable("Out of area"),
template: "modalOutOfArea",
closeLabel: "Close",
primaryLabel: "Continue",
cancelLabel: "Change Address",
show: ko.observable(false), /* Set to true to show initially */
sending: ko.observable(false),
onClose: function ()
{
model.EditEmailModel.show(false);
},
onAction: function () {
makePayment.execute();
},
onCancel: function ()
{
model.EditEmailModel.show(false);
}
};
You will have two async commands actually for this scenario. One to open up the modal and another one for the modal.
Eg:
showPaymentPromptCmd = ko.asyncCommand({
execute: function(complete) {
if (modalRequired) {
showModal();
} else {
makePayement();
}
complete();
},
canExecute: function(isExecuting) {
return !isExecuting;
}
});
//Called by Continue button on your modal.
makePaymentCmd = ko.asyncCommand({
execute: function(complete) {
makePayement();
complete();
},
canExecute: function(isExecuting) {
return !isExecuting;
}
});
var
function makePayement() {
//some logic
}
So I have a route that sets my template
Router.route('audit', {
path: '/audit/:audit_id/',
template: 'audit',
data: function() {
if (this.ready()) {
audit_obj = Audits.findOne({_id: this.params.audit_id});
lineitems = LineItems.find(JSON.parse(audit.query));
return {
audit_obj: audit_obj,
lineitems: lineitems
}
}
},
waitOn: function () {
return [
Meteor.subscribe('lineitems', this.params.audit_id),
Meteor.subscribe('audits')
]
}
}
Now, when my user takes certain actions on the page rendered by the audit template, I would like to update the audit object and also update the data context that the page is running with. Is this possible?
Something like:
Template.audit.events({
'click .something-button': function() {
// update the data context for the current audit template.
current_context.audit_obj.something = 'new something';
}
});
Yes:
Router.route('audit', {
path: '/audit/:audit_id/',
template: 'audit',
onRun: function() {
Session.set('audit', Audits.findOne(this.params.audit_id));
Session.set('lineitems', LineItems.find(JSON.parse(audit.query)).fetch());
}
data: function() {
if (this.ready()) {
return {
audit_obj: Session.get('audit'),
lineitems: Session.get('lineitems')
}
}
},
waitOn: function () {
return [
Meteor.subscribe('lineitems', this.params.audit_id),
Meteor.subscribe('audits')
]
}
}
and
Template.audit.events({
'click .something-button': function() {
// update the data context for the current audit template.
Session.set('audit', {..});
}
});
But you'll need to decide how to handle changes that come from the server, and may interfere with changes on the front end. So a better approach might be to leave the first part of the code (router) as is:
Router.route('audit', {
path: '/audit/:audit_id/',
template: 'audit',
data: function() {
if (this.ready()) {
return {
audit_obj: Audits.findOne(this.params.audit_id),
lineitems: LineItems.find(JSON.parse(audit.query))
}
}
},
waitOn: function () {
return [
Meteor.subscribe('lineitems', this.params.audit_id),
Meteor.subscribe('audits')
]
}
}
and just change the front end to update the collection:
Template.audit.events({
'click .something-button': function() {
// update the data context for the current audit template.
Audits.update( this.data.audit_obj._id, {..} );
}
});
Of course, that will update the data on the server, too.
I'm using Redactor and need to dynamically add elements to a custom dropdown menu. I can't find any way of doing this in the documentation - does anyone know if this is possible?
Yes, it's possible if you use this:
$('#redactor').redactor({
focus: true,
buttonsAdd: ['|', 'button1'],
buttonsCustom: {
button1: {
title: 'Button',
callback: function(buttonName, buttonDOM, buttonObject) { /* … */ },
dropdown: {
alignleft: {
title: lang.align_left,
func: 'alignmentLeft'
},
aligncenter: {
title: lang.align_center,
func: 'alignmentCenter'
}
}
}
}
});