I have an actionsheet that seems to only call the clickedButtonAtIndex delegate method when I click the "Yes" button, but not on the "No" button... Here's the code:
self.myActionSheet = [[UIActionSheet alloc] initWithTitle:#"Test" delegate:self
cancelButtonTitle:#"No" destructiveButtonTitle:#"Yes" otherButtonTitles:nil];
[self.myActionSheet showInView:self.view];
[myActionSheet release];
then the delegate method:
- (void)actionSheet:(UIActionSheet *)myActionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
[self.myActionSheet dismissWithClickedButtonIndex:1 animated:YES];
return;
}
My breakpoint at the first line of this procedure doesn't get hit unless I touch the
"Yes" button. I have changed this to have the cancelButtonTitle:nil and then put the "No" on another button (otherButtonTitles:#"No", nil). Same thing.
Any help?
Thanks!
Try touching the very top of the No button. Does it work?
Do you have a tab bar or a tool bar? If so, try showing the action sheet from the tab bar or the tool bar. The No button could be partially obscured by the bar.
No, its not a bug.. just a "Real Estate" issue.
take a look at the API.. from the UIActionSheet header...
// show a sheet animated. you can specify either a toolbar, a tab bar, a bar butto item or a plain view. We do a special animation if the sheet rises from
// a toolbar, tab bar or bar button item and we will automatically select the correct style based on the bar style. if not from a bar, we use
// UIActionSheetStyleDefault if automatic style set
- (void)showFromToolbar:(UIToolbar *)view;
- (void)showFromTabBar:(UITabBar *)view;
- (void)showFromBarButtonItem:(UIBarButtonItem *)item animated:(BOOL)animated __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_3_2);
- (void)showFromRect:(CGRect)rect inView:(UIView *)view animated:(BOOL)animated __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_3_2);
- (void)showInView:(UIView *)view;
Related
I have a class that has an extension of UIButton that shows a UIAlertview under certain circumstance.
#interface CellButton : UIButton {}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"You Lose!"
message:#"Play Again?"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:#"cancel", nil];
[alert show];
This works fine, but I need to present a view controller when user presses ok.But as you may know you cannot present a view controller with an extension of UIButton.
So I was wondering if I can put the code below in another viewcontroller and allow it to work with the UIAlert in Cellbutton class.
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) { // and they clicked OK.
GameController*myNewVC = [[GameController alloc] init];
[self presentModalViewController:myNewVC animated:NO];
}
}
You don't do it inside the UIButton.
The target of clicking the UIButton should be a UIViewController. After that, show an alert view FROM the view controller, and the view controller will be the delegate of the UIButton. From their everything will work fine.
This would work as long as you have set the alert view delegate to the view controller you want to handle the presentation.
I would suggest moving all of the functionality to the view controller though, i.e. present and handle the alert view from the same view controller, this can be triggered from an event from the button. I think this makes the code more readable and it doesn't really make sense for a button to know about alert views
You can declare a global variable (in appDelegate - and how to do it HERE) then ;
1 - set the variable 1 when user click button
2 - get the value from other viewcontroller's action
if the value is 1 then go ahead.
I have a tab bar with 4 buttons (Home, Visits, Share, More). I want the "Share" button in my tab bar to call an actionsheet which has the appropriate buttons to select the particular channels I want the user to be able to distribute links to the app (Facebook, Twitter, Email, etc.).
I'm able to do this using Storyboard but I have to create a unique (blank) view controller that is linked to the "Share" button in the tab bar. I put the following code within the viewDidAppear method to display the actionsheet when the "Share" button is selected:
int selectedTab = self.tabBarController.selectedIndex;
if (selectedTab == 2)
{
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:#"Share Your Visit with Friends!" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"Facebook", #"Twitter", #"Email eduLaunchpad", nil];
[actionSheet showFromTabBar:self.tabBarController.tabBar];
}
When I close the actionsheet, I then return by default to the home view:
[self.tabBarController setSelectedIndex:0];
While this works, it isn't optimal from a user perspective. I would prefer the actionsheet to appear over the particular view that was displaying when the "Share" button was selected. For example, if the user was on the "Visits" view and selected "Share" from the tab bar, I would like the actionsheet to appear over the top of the visits view with that view being visible behind the actionsheet.
Am I able to achieve this using Storyboard? Or do I need a custom tab bar in order to implement this functionality? If a custom tab bar is required, I would appreciate some guidance/insight into how to implement that including the custom tab bar as well as the actionsheet from the tab bar.
Thanks in advance!
I needed this, too, googled around, and eventually built the following category on UITabBarController.
The trick to getting the action sheet to appear on the current tab is that you never want to actually visit that action-sheet-presenting tab. Cover the tab bar in that position with a UIButton which initiates the action sheet.
Paint your tab bar as you normally would in storyboard, leaving an empty bar item at the position where the special behavior is to occur. Then add this category...
// UITabBarController+Button.h
#interface UITabBarController (Button) <UIActionSheetDelegate>
- (void)replaceItemAtIndex:(NSUInteger)index withButtonImage:(UIImage*)buttonImage;
#end
// UITabBarController+Button.m
#import "UITabBarController+Button.h"
#define kBUTTON_TAG 4096
#implementation UITabBarController (Button)
- (void)replaceItemAtIndex:(NSUInteger)index withButtonImage:(UIImage*)buttonImage {
UIButton *button = (UIButton *)[self.view viewWithTag:kBUTTON_TAG];
if (!button) {
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.tag = kBUTTON_TAG;
[button setBackgroundImage:buttonImage forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
UITabBar *bar = self.tabBar;
CGFloat width = bar.frame.size.width / bar.items.count;
button.frame = CGRectMake(index*width, bar.frame.origin.y, width, bar.frame.size.height);
[self.view addSubview:button];
}
}
#pragma mark - Handle button tap
- (void)buttonTapped:(id)sender {
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:#"Action Sheet:"
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"Action A", #"Action B", #"Action C", nil];
[sheet showFromTabBar:self.tabBar];
}
Call it with an index < tabBar.items.count and an image for the button. If it's your app's root vc, you can call it as follows:
#import "the category i suggest above"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// whatever else you do in here, then
NSLog(#"%#", self.window.rootViewController); // make sure this is a UITabBarController
// if it is, then ...
UITabBarController *tbc = (UITabBarController *)self.window.rootViewController;
[tbc replaceItemAtIndex:2 withButtonImage:[UIImage imageNamed:#"some image name"]];
return YES;
}
Instead of opening actionsheet from tabbar
[actionSheet showFromTabBar:self.tabBarController.tabBar];
use this code.
[actionSheet showInView:[UIApplication sharedApplication].keyWindow];
I have an educational app working fine with tab bars. One of the tabs is a test for the user. If a user taking the test selects another tab, I would like an action sheet to display to confirm they really want to exit the test since their test results will be lost.
I can't get the action sheet to display. I am getting syntax errors with the first three examples below. The 4th example will compile OK but the app aborts. I can't figure out what should go after the self. Or possibly one of the other examples would work if I had the syntax correct.
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController {
NSLog(#"Tab Bar Controller reached");
UIActionSheet *actionSheet = [[UIActionSheet alloc]
initWithTitle:#"This will end the test.
Are you sure you want to leave?"
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:#"Yes,
cancel the test."
otherButtonTitles:nil];
[actionSheet showInView:self.view];
[actionSheet showInView:elements1AppDelegate.window];
[actionSheet showFromTabBar:self.tabBarController.tabBar];
[actionSheet showFromTabBar:self];
Ultimately, once I get the action sheet to display, I will either proceed to the tab selected or stay in the test view depending on whether the user decides to exit or not. Any help/suggestions would be appreciated. Thanks.
The name of the method tabBarController:didSelectViewController: should indicate that it's too late to stop the selection. Its return type void indicates there is not much you can do about it. Instead focus on method names that have "will" or "should" in them, and return types like BOOLs such as tabBarController:shouldSelectViewController:. So here is some basic code that does what you want.
I don't know the actual classname of your test's view controller so I'll use QuizController as a classname. QuizController is a UIViewController subclass.
QuizController.m
#interface QuizController () <UITabBarControllerDelegate,UIActionSheetDelegate>{
BOOL _testInProgress;
}
#end
#implementation QuizController
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// When a tabs view controller is presented viewDidAppear: will be called so here we will set this view controller as the tabBarController delegate so we get the callback.
self.tabBarController.delegate = self;
}
-(void)startQuiz{
_testInProgress = YES;
// Begin testing code
}
-(void)stopQuiz{
// Score test record results
_testInProgress = NO;
}
-(void)cancelQuiz{
// Throw out results
_testInProgress = NO;
}
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController{
if (!_testInProgress) return YES;
// If trying to select this controller then who cares?
if (viewController == self) return YES; // Or NO. Just don't show the sheet.
UIActionSheet *action = [[UIActionSheet alloc] initWithTitle:#"You are in the middle of a test. Are you sure you want to switch tabs?"
delegate:self
cancelButtonTitle:#"Continue Test"
destructiveButtonTitle:#"Abort Test"
otherButtonTitles:nil];
// Lets cheat and use the tag to store the index of the desired view controller.
action.tag = [self.tabBarController.viewControllers indexOfObject:viewController];
[action showFromTabBar:self.tabBarController.tabBar];
return NO;
}
-(void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex{
if (buttonIndex == actionSheet.destructiveButtonIndex){
[self cancelQuiz];
// The above cheat pays off.
[self.tabBarController setSelectedIndex:actionSheet.tag];
}
}
EDIT (In response to comment quoted)
I'm a little confused about your example. Currently, my "take test"
class is a UIViewController. Are you suggesting I replace that with
your QuizController above?
No. I am suggesting that you take this code and integrate the design pattern into your UIViewController subclass that handles your test. Although this is a working example (providing you supply UI to toggle the _testInProgress ivar.)
Do I leave my current UITabBarController in place?
Yup.
That is currently my appdelegate and rootController.
Huh? Your UITabBarController is almost certainly your rootViewController. But unless you have done something very odd like AppDelegate : UITabBarController <UIApplicationDelegate>, by the way don't do that, then it is extremely unlikely that your "appdelegate" and your UITabBarController are the same. Much more likely your AppDelegate is your tabBarController's delegate.
If just setting the tabBarController.delegate property on appearance is bad (and it could very well be), i.e. some other object needs to be the tabBarController's delegate, then you'll have to forward a message to that view controller to see if a test is in progress. For this you could actually leave almost all of the code in the example unchanged. Of course you would have to remove the self.tabBarController.delegate = self; in viewDidAppear:. And put the following in your AppDelegate(presuming that's the tabBarController's delegate):
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController{
if ([tabBarController.selectedViewController respondsToSelector:#selector(tabBarController:shouldSelectViewController:)]){
return [(NSObject <UITabBarControllerDelegate>*)tabBarController.selectedViewController tabBarController:tabBarController shouldSelectViewController:viewController];
}
return YES;
}
This implementation essentially forwards the responsibility to answer the question to the view controller, provided it will answer the question.
I check in there if the "take test" tab was selected and call an
initialization method in my "take test" class.
In my opinion the "take test" view controller should simply become selected when the user taps the tab for it. And its view should contain a button with something to the effect of 'start test' on it. But that's just my opinion.
But whatever the case is the application's knowledge of whether the user is taking a test should reside in the view controller that administers the test.
[actionSheet showInView:[[[UIApplication sharedApplication] windows] objectAtIndex:0]];
Did you try this One Link
In my UIViewController I have a UINavigationController with a default back button. When the user clicks the back button, a warning message should appear: "Do you really want to go back?". I know, that it is not possible to trap the back button event. It's only possible the use viewWillDisappear and set a flag:
- (void)viewWillDisappear:(BOOL)animated {
if (backBtnPressed) {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:#"Question" message:#"Do you really want to go back?" delegate:self cancelButtonTitle:#"No" otherButtonTitles: #"Yes", nil] autorelease];
[alert show];
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
// don't go back!
// cancel the back button event
}
else if (buttonIndex == 1) {
// go back
}
}
But with this code I have no chance! I can't stop the back button event, isn't it?
Do I have to write my own back button and set it as leftBarButtonItem? Or is there anybody with a great idea? :-)
Thanks for your help!
My answer from another thread matches this question. So I repost it here:
I've implemented UIViewController-BackButtonHandler extension. It does not need to subclass anything, just put it into your project and override navigationShouldPopOnBackButton method in UIViewController class:
-(BOOL) navigationShouldPopOnBackButton {
if(needsShowConfirmation) {
// Show confirmation alert
// ...
return NO; // Ignore 'Back' button this time
}
return YES; // Process 'Back' button click and Pop view controller
}
Download sample app.
What you need to do is to use the delegate of the navigation bar, and not the navigation controller.
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item; // called to push. return NO not to.
- (void)navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item; // called at end of animation of push or immediately if not animated
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item; // same as push methods
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item;
viewWillDisappear is a delegate method for the event that the view is going to disappear - and there's nothing the developer can do about that! If you could, it would be a viewShouldDisappear delegate method.
So I guess the only way is as you suggest, to use a custom leftBarButtonItem.
I must say this is one of the common use cases that Apple doesn't seem to make easy, and I see a lot of effort trying to get this working. I thought maybe I should summarize my findings here.
As many have pointed out, the method below in UINavigationBarDelegate is key to implementing this feature.
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
Many have subclassed UINavigationController and implemented the method above to make it easy to use without direct access to the UINavigationBar.
Unfortunately, there still remain some issues.
The swipe back gesture does not invoke this method.
Although it seems necessary, crashes are reported calling popViewControllerAnimated: in this method.
The Back button remains grayed out, when pop is cancelled.
Swipe back gesture
We need to intercept the gesture by setting the delegate as is done in https://stackoverflow.com/a/23173035/2400328 .
If the UINavigationController is subclassed, that would be:
self.interactivePopGestureRecognizer.delegate = self
and implementing:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
Take care in when you modify the delegate property, as it gets modified after the initializer is called.
Not calling popViewControllerAnimated:
Although undocumented, calling popViewControllerAnimated: can be avoided as in https://stackoverflow.com/a/26084150/2400328.
It involves calling navigationBar:shouldPopItem: of UINavigationController (from the subclass).
The Back button
Although this may be a minor detail (especially, if you have designed your own Back button), there is a simple solution (written by me :) https://stackoverflow.com/a/29440633/2400328
You only need to set a property YES and NO.
auto item = navigationBar.topItem;
item.hidesBackButton = YES;
item.hidesBackButton = NO;
You can use a custom button with a graphics, which looks exactly like "Back" button and create a custom leftBarButtonItem view as UIButton with this graphics. Add target self to your button with custom back: selector and pop your alert there. If the user presses "yes" to quit dismiss this view controller, if not - do nothing. The trick here is the button which looks exactly as navigation bar's back button.
Its better if u make your own back button and make it the left button of the Navigation controller. That can definitely help u to perform any action
If you're looking for a way to do this in Swift on iOS 10, you can create a custom UINavigationController and then a UINavigationBarDelegate extension:
class MyNavigationController : UINavigationController {
}
extension MyNavigationController : UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return false
}
}
The Method
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
is doing what you want. Unfortunately we are not supposed to delegate UINavigationBar to our own objects :(
The Apple Documentation states :
... In addition, a navigation controller object automatically assigns itself as the delegate of its UINavigationBar object and prevents other objects from changing that relationship. ...
One/The? way to do what you want is to put in your own back-button.
In that Method you do your tests and call
[self.navigationController popViewControllerAnimated:true];
if the user is allowed to go back.
I learned how to create a view controller and make the view slide in from bottom. But the one in iphone album looks different. It darkens the rest of the visible portion of the screen when the view slides in. How do I create a similar one? I want to add buttons like "save, cancel, email" etc into the sliding view.
This actually isn't a typical "sliding" (or modal) view, but a UIActionSheet. Basically, the idea is that you initialize the view (usually in your view controller) with
UIActionSheet *sheet =
[[[UIActionSheet alloc] initWithTitle:#"My sheet"
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"Email", #"MMS", nil] autorelease];
Then present it using
[sheet showInView:[self view]];
Once it's onscreen, the delegate (self, or your view controller, in this example) will receive the UIActionSheetDelegate message actionSheet:clickedButtonAtIndex: (as well as some others; see the documentation for more), so you'll want to add <UIActionSheetDelegate> to your interface declaration for the delegate and implement that method, like
- (void)actionSheet:(UIActionSheet *)actionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex {
switch(buttonIndex) {
// Do things based on which button was pushed
}
}