Versions of iOS prior to 8 allowed me to create a UIActionsheet which would show a group of buttons, some space, and then a cancel button. Something like this:
However in iOS 8 when I try and create the same look I end up with something that looks like this:
The code, in iOS 8 looks like this:
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[alertVC.view setTintColor:[UIColor copperColor]];
UIAlertAction* notifyViaPush = [UIAlertAction
actionWithTitle:#"Send Alert to my phone"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alertVC dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* notifyViaEmail = [UIAlertAction
actionWithTitle:#"Notify me by email"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alertVC dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* cancel = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action)
{
[alertVC dismissViewControllerAnimated:YES completion:nil];
}];
[alertVC addAction:notifyViaPush];
[alertVC addAction:notifyViaEmail];
[alertVC addAction:cancel];
[self presentViewController:alertVC animated:YES completion:nil];
How can I group my buttons and have some space between the actions and cancel button using UIAlertController?
The line alertVC.view.tintColor = [UIColor copperColor]; is causing problem, it makes the whole view of alert controller the same color, in your first picture the cancel button has white background. To fix this, move this line to the end of the function, that is, after you have added all actions.
For me it worked when I set the style to UIAlertActionStyleCancel on the UIAlertAction.
In the code below, action is a UIAlertAction object and controller is a UIAlertController with style Action sheet.
UIAlertAction *action2 = [UIAlertAction actionWithTitle:#"No" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
self.tabBarView.selectedIndex = 1;
[self.tabBarView setSelectedIndex:self.tabBarView.selectedIndex];
}];
[controller addAction:action];
[controller addAction:action2];
Swift Answer
Set the style: parameter of the action button that you want to separate to .cancel instead of .default.
like this style: .cancel
or more specifically this
// **style: on this is set to .cancel**
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in }
It's usually the button that you named "Cancel"
I'm not sure about how setting the button text colors the way the op said he did it in his answer affects the way the actions get separated. I use the actions "titleTextColor" to change the text color and it has no effect on the spacing the op is asking about.
cancelAction.setValue(UIColor.green, forKey: "titleTextColor")
Here's the code in 4 steps
func presentActionSheet() {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 1. style: is set to .default on both of these which will keep them grouped
let blockAction = UIAlertAction(title: "Block", style: .default) { (action) in }
let reportAction = UIAlertAction(title: "Report", style: .default) { (action) in }
// 2. style: is set to .cancel which will separate it from the other two actions
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in }
// 3. set the colors for the action text
blockAction.setValue(UIColor.brown, forKey: "titleTextColor")
reportAction.setValue(UIColor.purple, forKey: "titleTextColor")
cancelAction.setValue(UIColor.green, forKey: "titleTextColor")
// 4. add the buttons to the action sheet and make sure the cancel button is last
actionSheet.addAction(blockAction)
actionSheet.addAction(reportAction)
actionSheet.addAction(cancelAction)
present(actionSheet, animated: true, completion: nil)
}
Related
I have a class named ManagerClass.
Manager Class has a function showUIAlertController:
- (UIAlertController*)showUIAlertController:(NSString *)title message:(NSString *)message actions:(NSArray<UIAlertAction*>* )actions
This function should show alert controller with the parameters received.
So far so good...
Now i would like to take these actions and edit them somehow. Something like:
UIAlertAction *action = actions.firstObject;
UIAlertAction *actionCopyWithAdditionalAction = [UIAlertAction actionWithTitle:action.title style:action.style handler:^(UIAlertAction * _Nonnull action) {
[action "performTheAction"]; //perform the original action
[ManagerClass doSomething];
}];
"performTheAction" does not exist - it is just for you to understand what i am trying to achieve.
Does anyone has an idea how this task can be achieved ?
Did not find a way to do that while looking at Apple's UIAlertAction API
https://developer.apple.com/reference/uikit/uialertaction
Do you mean to perform a method provided by your code. Then use:
[self performSelector:#selector(aMethod:)];
or when sending an object with:
[self performSelector:#selector(aMethod:)
withObject:(id)object];
Note, the self here is referencing to the same class, it could be somewhere else as well.
Edit *
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"My Alert"
message:#"This is an alert."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
NSLog(#"42.");
}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
The console logs out 42. Put instead every action do you need.
Why you want to call a second alert which only displays the first alert and execute some of your code? You can do that in the first alert, too.
//Create the UIAlertController
UIAlertController *theAlertController = [UIAlertController alertControllerWithTitle:#"Your Title" message:#"Your Message" preferredStyle:UIAlertControllerStyleAlert];
//Add an UIAlertAction which the user can click at
[theAlertController addAction:[UIAlertAction actionWithTitle:#"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
//Execute your own code
//[self myOwnCode];
//Close the AlertController after your code
[self dismissViewControllerAnimated:YES completion:nil];
}]];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:theAlertController animated:YES completion:nil];
});
Hope i understand you right.
You could pass a alert action model instead of a UIAlertAction.
so your method would look something like this:
- (UIAlertController*)showUIAlertController:(NSString *)title message:(NSString *)message actions:(NSArray<MyActionModel*>* )actions
where MyActionModel is a class with 3 properties
#interface MyActionModel: NSObject {
#property NSString * title;
#property UIAlertActionStyle * style;
#property ((void)^(UIAlertAction *)) action;
}
Then you can create your UIAlertActions when you need them and also add in your manager callbacks.
P.S. Sorry if my Objective-C is not quite right, I'm a bit rusty.
HeyI want to do that This my button and in button there is textfield I want to do that when I pressed on button the action sheet picker become appear and give 4 to 5 list of string whatever I select it will apear on textfield which is in button. please help me
Start by adding a target for your button. In Objective-C, that would be like this:
[myButton addTarget:self
action:#selector(buttonPressed:)
forControlEvents:UIControlEventTouchUpInside];
Then create the method buttonPressed. An example of that would be:
- (void)buttonPressed:(id)sender {
if ([sender isEqual:self.myButton]) {
//This is where you can create the UIAlertController
}
}
Then, to create the UIAlertController:
UIAlertController *myAlertController = [UIAlertController alertControllerWithTitle:#"Title"
message:#"Message"
preferredStyle:UIAlertControllerStyleActionSheet];
Then you create actions for what each button you want to have appear on the action sheet. You need to have a title for the button and an action for them, though the action block can be empty.
UIAlertAction *action1 = [UIAlertAction actionWithTitle:#"Action 1"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
//Whatever you want to have happen when the button is pressed
}];
[myAlertController addAction:action1];
//repeat for all subsequent actions...
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:NSLocalizedString(#"Cancel", nil)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
// It's good practice to give the user an option to do nothing, but not necessary
}];
[myAlertController addAction:cancelAction];
Lastly, you present the UIAlertController:
[self presentViewController:myAlertController
animated:YES
completion:^{
}];
NOTE:
If you're building for iPad and using an Action Sheet style for the UIAlertController, then you will need to set a source for the UIAlertController to present from. This can be done like this:
if ([sender isKindOfClass:[UIView class]]) {
if ([myAlertController.popoverPresentationController respondsToSelector:#selector(setSourceView:)]) { // Check for availability of this method
myAlertController.popoverPresentationController.sourceView = self.myButton;
} else {
myAlertController.popoverPresentationController.sourceRect = self.myButton.frame;
}
}
Here's the problem up front: I have a UIAlertController that has a textfield. I want to save the content of that textfield as an NSString when the user touches a "Confirm" button in the alert. When the Confirm action block is executed, however, the alert is nil (presumably already dismissed and deallocated at that point), and thus so is its textfield, meaning I cannot save the textfield's text.
I am using a series of UIAlertControllers to allow a user to create a passcode for my app, such that any time the app comes to the foreground, the user is prompted for the code before the app can be used.
I created a category of UIAlertController with several convenience methods that return preconfigured alerts that I need to use. Here's one of them:
+ (UIAlertController*)passcodeCreationAlertWithConfirmBehavior:(void(^)())confirmBlock andCancelBehavior:(void(^)())cancelBlock {
UIAlertController *passcodeCreationAlert = [UIAlertController alertControllerWithTitle:#"Enter a passcode"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[passcodeCreationAlert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.keyboardType = UIKeyboardTypeNumberPad;
}];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
if (cancelBlock) {
cancelBlock();
}
}];
UIAlertAction* confirmAction = [UIAlertAction actionWithTitle:#"Confirm"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (confirmBlock) {
confirmBlock();
}
}];
[passcodeCreationAlert addAction:cancelAction];
[passcodeCreationAlert addAction:confirmAction];
passcodeCreationAlert.preferredAction = confirmAction;
confirmAction.enabled = NO;
return passcodeCreationAlert;
}
This method returns a UIAlertController that allows the user to enter their desired passcode into a textfield. When I call this method in my view controller, I pass blocks as parameters which are used as the UIAlertAction handlers:
- (void)presentCreatePasscodeAlert {
UIAlertController *alert = [UIAlertController passcodeCreationAlertWithConfirmBehavior:^{
firstPasscode = alert.textFields[0].text;
[self presentConfirmPasscodeAlert];
} andCancelBehavior:^{
[self presentEnablePasscodeAlert];
}];
alert.textFields[0].delegate = self;
[self presentViewController:alert animated:YES completion:nil];
}
To reiterate the problem now that there is more context: When the action block is entered at the line:
firstPasscode = alert.textFields[0].text;
the alert is nil, and so is its textfield, meaning I cannot save the textfield's text.
In a separate project, however, I tried getting the same functionality without using the category and custom convenience methods, and that works as desired:
- (void) createPassword {
UIAlertController *createPasswordAlert = [UIAlertController alertControllerWithTitle:#"Enter a password"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
__weak ViewController *weakSelf = self;
[createPasswordAlert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.delegate = weakSelf;
textField.keyboardType = UIKeyboardTypeNumberPad;
}];
UIAlertAction* confirmAction = [UIAlertAction actionWithTitle:#"Confirm"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
self.password = createPasswordAlert.textFields[0].text;
[self confirmPassword];
}];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self promptPasswordCreation];
}];
[createPasswordAlert addAction:confirmAction];
[createPasswordAlert addAction:cancelAction];
confirmAction.enabled = NO;
[self presentViewController:createPasswordAlert animated:YES completion:nil];
}
Sure enough, in the above code the alert exists when the Confirm block is entered, and I can save the text just fine.
Have I done something screwy by passing blocks as parameters to my convenience methods?
One of your problems is that when you create the block and send it into the initializer you are referencing a nil object inside the block. Then the block is saved with that nil reference and passed as a block to your handler.
UIAlertController *alert = [UIAlertController passcodeCreationAlertWithConfirmBehavior:^{
firstPasscode = alert.textFields[0].text;
[self presentConfirmPasscodeAlert];
} andCancelBehavior:^{
[self presentEnablePasscodeAlert];
}];
In there alert is not initialized yet when you create that block and send it to be saved. An option to fix that would be to use a standard initializer and then categorize a method to add the block functions that you want. Another option might be to try to add the __block modifier to the alert object, although I don't think that will help.
UIAlertController with two buttons with styles set:
UIAlertActionStyle.Cancel
UIAlertActionStyle.Default
in iOS 8.2, the Cancel button is non-bold and Default is bold.
In iOS 8.3 they have switched round
You can see it Apple's own apps e.g., Settings > Mail > Add Account > iCloud > enter invalid data, then it shows like this on 8.3:
Unsupported Apple ID
Learn More (bold)
OK (non-bold)
whereas it was the other way round for 8.2.
Any workaround to make it like 8.2 again. Why has it changed?
From iOS 9 you can set the preferredAction value to the action which you want the button title to be bold.
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
let OKAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alert.addAction(cancelAction)
alert.addAction(OKAction)
alert.preferredAction = OKAction
presentViewController(alert, animated: true) {}
The OK button which is on the right will be in bold font.
This is an intentional change to the SDK. I have just had a response from Apple to this radar on the issue, stating that:
This is an intentional change - the cancel button is to be bolded in alerts.
I can't find anything in the various change logs mentioning this, unfortunately.
So, we'll need to make changes to our apps in places to make some things make sense.
Since iOS 9, UIAlertController has a property called preferredAction. preferredAction has the following declaration:
var preferredAction: UIAlertAction? { get set }
The preferred action for the user to take from an alert. [...] The preferred action is relevant for the UIAlertController.Style.alert style only; it is not used by action sheets. When you specify a preferred action, the alert controller highlights the text of that action to give it emphasis. (If the alert also contains a cancel button, the preferred action receives the highlighting instead of the cancel button.) [...] The default value of this property is nil.
The Swift 5 / iOS 12 sample code below shows how to display a UIAlertController that will highlight the text of a specified UIAlertAction using preferredAction:
let alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { action in
print("Hello")
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
alertController.preferredAction = okAction
present(alertController, animated: true, completion: nil)
I just checked in iOS 8.2: a first added button is non-bold and a second added button is bold. With this code a cancel button will be bold:
[alertController addAction:[UIAlertAction actionWithTitle:#"Ok"
style:UIAlertActionStyleDefault
handler:nil]];
[alertController addAction:[UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
And with this code a default button will be bold:
[alertController addAction:[UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:nil]];
[alertController addAction:[UIAlertAction actionWithTitle:#"Ok"
style:UIAlertActionStyleDefault
handler:nil]];
I can't check in iOS 8.3 now but this behavior can be a reason.
Some words about objective-c and preferredAction for alertActions. If you use preferredAction you BOUTH alertAction must set as style:UIAlertActionStyleDefault. If some one will be set as style:UIAlertActionStyleCancel, preferredAction will be ignored
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"All you base"
message:#"Are belong to us!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* alertActionShowYes = [UIAlertAction actionWithTitle:#"YES!" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSLog(#"I serve for my emperor!");
}];
UIAlertAction* alertActionNo = [UIAlertAction actionWithTitle:#"NO!" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSLog(#"NOOOO it's not true!!");
}];
[alertController addAction:alertActionShowYes];
[alertController addAction:alertActionNo];
alertController.preferredAction = alertActionShowYes;
[alertController setPreferredAction:alertActionShowYes];
[self presentViewController:alertController animated:YES completion:nil];
I have something like the following code. The Action sheet runs doSomething OK when it appears for the first time (in a button IBAction), but when it appears the second time, nothing happens the Action sheet just disappear without calling do something. Any idea?
#implementation ...
- (void) setActions {
UIAlertAction *opt1 = [UIAlertAction actionWithTitle:#"Option 1" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self doSomething:#"opt1"];}];
UIAlertAction *opt2 = [UIAlertAction actionWithTitle:#"Option 2" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self doSomething:#"opt2"];}];
UIAlertAction *opt3 = ...
self.opt1 = opt1;
self.opt2 = opt2;
self.opt3 = opt3;
- (void) showActionSheet {
...
UIAlertController *selectAS = [UIAlertController alertControllerWithTitle:#"Select Options"
message:#"msg" preferredStyle:UIAlertControllerStyleActionSheet];
if (xyz) {
[selectAS addAction:self.opt1];
[selectAS addAction:self.opt2];
}
else{
[selectAS addAction:self.opt1];
[selectAS addAction:self.opt3];
}
[self presentViewController:selectqAS
animated:YES completion:nil];
}
- (void) doSomething: (NSString *) opt{
....
}
Glad we got you up and running. My guess is your methods are getting lost in translation. You have methods intertwining each other which can be causing the confusion, specifically with self.opt1. per my comment, now that iOS8 has introduced UIAlertController, it comes with completion handlers, you should plan accordingly to that: something like the following :
-(IBAction)showActionSheet {
UIAlertController *selectAS = [UIAlertController alertControllerWithTitle:#"Select Options" message:#"msg" preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *opt1 = [UIAlertAction actionWithTitle:#"Option 1" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
//Don't have to call another method, just put your action 1 code here. This is the power of completion handlers creating a more structured outline
}];
UIAlertAction *opt2 = [UIAlertAction actionWithTitle:#"Option 2" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
//Don't have to call another method, just put your action 2 code here. This is the power of completion handlers creating a more structured outline
}];
UIAlertAction *opt3 = ...
if (xyz) {
[selectAs addAction:opt1];
[selectAs addAction:opt2];
} else {
[selectAs addAction:opt1];
[selectAs addAction:opt3];
}
[self presentViewController:selectAs animated:YES completion:nil];
}
Much more cleaner and actually uses the UIAlertController for it's intended purposes, no other method calls needed.