I have an app that displays a link when you scan a QR code.
With accessibility turned on (VoiceOver):
When I double click on the link I open a custom alert.
The problem is that when I double-click and hold on the link I still get the alert instead of the context menu.
My code:
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction {
BOOL linkScheme = ([URL.scheme isEqualToString: #"http"] || [URL.scheme isEqualToString: #"https"]);
if ([[UIApplication sharedApplication] canOpenURL:URL]){
NSUserDefaults *saveURL = [NSUserDefaults standardUserDefaults];
[saveURL setObject:URL.absoluteString forKey:#"SavedURL"];
[saveURL synchronize];
if (linkScheme) {
// if (detect double click and hold) {
return YES;
} else {
if(!GUIManager.instance.isAlwaysAskChecked){
if(GUIManager.instance.openInThisAppButtonChecked) {
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
WebViewController* webView = [[WebViewController alloc] init];
[topController showViewController:webView sender:nil];
} else {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:URL.absoluteString]];
}
} else {
tableV.accessibilityElementsHidden = YES;
AlertView* alertView = [[AlertView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.view addSubview: alertView];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, alertView);
}
return NO;
}
}
}
return YES;
}
I'd like to enable the context menu on double-click and hold (with accessibility (VoiceOver) enabled), instead of displaying an alert.
So this is happening.
I have one view controller and it handles all of the alert views in my app. I have another view controller that has a UITextView that the user can edit, and a save button.
When they hit the save button, if the text is already saved, it triggers an alert that asks them if they're sure they want to update, and if they confirm, it updates the file and gives them a second alert that says it was a success.
What's happening is that the keyboard keeps popping up when the second alert appears. I've tried resigning the keyboard and turning off the user interaction enabled flag on the text field as soon as the save button is hit.
self.storyEditorTextView.userInteractionEnabled=NO;
[self.storyEditorTextView resignFirstResponder];
I've also tried to turn it off when the alert is responded to (since some alerts can have a textfield).
To make matters worse, when I comment out the resign and userInteractionsEnabled lines, including the one in the alert, the keyboard still appears after the first alert is dismissed, disappears when the second alert is dismissed (if you can tap it because the keyboard covers it), and you can't tap into the UITextView and bring up the keyboard without going back to the parent view.
Here's the alert code.
- (void)addPromptToFavorites
{
// throw up an alert to confirm and get a name
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Name Your Favorite!"
message:#"Would you like to add a name to your prompt?"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK",nil];
// add a text field
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
UITextField *textField = [alert textFieldAtIndex:0];
textField.text = #"My Great Prompt";
// set the tag
[alert setTag:SAVE_FAVE];
// Display the alert to the user
[alert show];
}
- (void)updateFave: (NSNumber *) theFaveId
{
NSLog(#"UPDATE FAVE\n\nself.sharedFaveMan.tempFave %#",self.sharedFaveMan.tempFave);
// NSMutableDictionary *faveDict =[[NSMutableDictionary alloc] init];
// loop through the favoritePrompts array until you find a match to the faveID
for (id element in self.sharedFaveMan.favoritePrompts) {
NSNumber *valueForID = [element valueForKey:#"id"];
if([valueForID isEqualToNumber:theFaveId])
{
self.sharedFaveMan.tempFave=element;
}
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Update Your Favorite!"
message:#"The story will be saved with the currently selected Favorite."
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK",nil];
//set the tag
[alert setTag:UPDATE_FAVE];
// Display the alert to the user
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
UITextField *textField = [alertView textFieldAtIndex:0];
[textField resignFirstResponder];
if (buttonIndex !=0)
{
if(alertView.tag==UPDATE_FAVE)
{
NSLog(#"Updating Fave");
// loop through the favoritePrompts array until you find a match to the faveID
int counter=0;
for (id element in self.sharedFaveMan.favoritePrompts) {
NSNumber *valueForID = [element valueForKey:#"id"];
if([valueForID isEqualToNumber:self.sharedFaveMan.theFaveID]){
break;
}
counter ++;
}
// update the pieces of the prompt
[[self.sharedFaveMan.favoritePrompts objectAtIndex:counter] setObject:self.sharedFaveMan.faveStoryText forKey:#"storyText"];
// save it
[self saveFavorites];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"updateTheTable"
object:self];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Success!"
message:#"Favorite Updated!"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"OK",nil];
//play a sound
[self createSoundID: #"ticktock.aiff"];
//set the tag
[alert setTag:UPDATE_COMPLETE];
// Display the alert to the user
[alert show];
}
}
else if (buttonIndex == 0)
{
NSLog(#"%ld",(long)alertView.tag);
if(alertView.tag==SAVE_FAVE)
{
// they canceled the save
}
else if (alertView.tag==UPDATE_COMPLETE)
{
NSLog(#"Hit that");
[[NSNotificationCenter defaultCenter]
postNotificationName:#"dismissedDialogNotification"
object:self];
}
}
}
-(BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView
{
UITextField *textField = [alertView textFieldAtIndex:0];
if (textField && [textField.text length] == 0)
{
return NO;
}
return YES;
}
Any ideas?
Here is the code
- (void)textViewDidEndEditing:(UITextView *)textView {
[textView resignFirstResponder];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#"\n"]) {
[textView resignFirstResponder];
// Return FALSE so that the final '\n' character doesn't get added
return NO;
}
// For any other character return TRUE so that the text gets added to the view
return YES;
}
-(BOOL)textViewShouldEndEditing:(UITextView *)textView {
[textView resignFirstResponder];
return true;
}
and in alert method at the begining add this line of code
[self.view endEditing:YES];
Is there any way to judge whether there is currently a UIAlertView instance showing? Because it is possible to show multiple UIAlertView in the same window level.
if ([self isAlertViewShowing]) {
// do not show UIAlertView again.
} else {
// show your UIAlertView.
}
Hope there is such a method called isAlertViewShowing or something else.
Method 1-
Initialize default flag for alert... If alert is not open set isAlertViewShowing as NO
Bool isAlertViewShowing;
isAlertViewShowing = NO;
if (isAlertViewShowing == NO){
UIAlertView *alert =[[UIAlertView alloc]initWithTitle:#"Title" message:#"Message" delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
// Now set isAlertViewShowing to YES
isAlertViewShowing = YES;
}
else
{
//Do something
}
Method 2-
Make your own function to check whether any UIAlertView is showing or not
- (BOOL)isAlertViewShowing{
for (UIWindow* window in [UIApplication sharedApplication].windows) {
NSArray* subviews = window.subviews;
if ([subviews count] > 0){
for (id view in subviews) {
if ([view isKindOfClass:[UIAlertView class]]) {
return YES;
}
}
}
}
return NO;
}
I recommended to use second method if number of UIAlertView instance
may be more than one.
I have an action sheet that when one of it's options are clicked successfully calls clickedButtonAtIndex when run in the simulator but when testing on an iPhone (5s in Xcode 6) it doesn't reach the callback.
The header...
#protocol SGETriggerToolBarDelegate
-(void)showCustomEditView;
#end
#interface SGETriggerToolBarController : UIViewController <UIActionSheetDelegate>
#property (nonatomic, assign) id <SGETriggerToolBarDelegate> delegate;
#property (nonatomic, strong) UIToolbar *toolbar;
in the implementation...
// in xController.m
// ...
- (void)triggerButtonHandler
{
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:#"Select an event type"
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:nil];
for (SGETrigger *trigger in triggers) {
[actionSheet addButtonWithTitle:trigger.name];
}
[actionSheet addButtonWithTitle:#"Cancel"];
actionSheet.cancelButtonIndex = triggers.count;
[actionSheet showFromToolbar:self.toolbar];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == [actionSheet cancelButtonIndex]) {
return;
} else {
selectedTrigger = triggers[buttonIndex];
triggerButton.title = [NSString stringWithFormat:#"• %# •", selectedTrigger.name];
[delegate showCustomEditView];
}
}
// ...
If you get "Presenting action sheet clipped by its superview. Some controls might not respond to touches"
Replacing...
[actionSheet showFromToolbar:self.toolbar];
with
[actionSheet showInView:[UIApplication sharedApplication].keyWindow];
solved this for me.
I want to have a persistent button in the bottom right corner of my app. During all view transitions, the button should remain static. I'm having trouble deciding what view to add the button to. I know the button ought to be stored in the AppDelegate, but I don't know what other view it would be sense to add it to except the window. One downside of adding it to the window is that when there's an app running in the background (ie Phone), the added status bar padding will push down the window. In general, adding it to the window seems to be a hacky solution -- any thoughts?
Yes, adding it to the UIWindow would be extremely hacky and finicky.
Storyboards
If you're using Storyboards and iOS 5.0 onwards, you should be able to use container views and do something like this:
Here's another picture showing the, rather simplistic, structure of the first View Controller:
The view controller on the left has a container, and then a view which holds the button on top of it. The container indicates that the navigation controller (directly to the right) should appear within itself, that relationship is shown by the =([])=> arrow (formally known as an embed segue). Finally the navigation controller defines its root view controller to the one on the right.
In summary, the first view controller pancakes-in the container view with the button on top, so everything that happens inside has to have the button on top.
Using childViewControllers
aka. The "I hate Storyboards and puppies" mode
Using a similar structure to the Storyboard version, you could create the base view controller with its button, and then, add the view that will become then new "root" of the application, underneath.
To make it clear, let's call the one view controller that holds the button the FakeRootViewController, and the view controller that will be, for all practical purposes, the root of the application: RootViewController. All subsequent view controllers won't even know that there's the FakeRootViewController above everyone else.
FakeRootViewController.m
// The "real" root
#import "RootViewController.h"
// Call once after the view has been set up (either through nib or coded).
- (void)setupRootViewController
{
// Instantiate what will become the new root
RootViewController *root = [[RootViewController alloc] <#initWith...#>];
// Create the Navigation Controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:root];
// Add its view beneath all ours (including the button we made)
[self addChildViewController:nav];
[self.view insertSubview:nav.view atIndex:0];
[nav didMoveToParentViewController:self];
}
AppDelegate.m
#import "FakeRootViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
FakeRootViewController *fakeRoot = [[FakeRootViewController alloc] <#initWith...#>];
self.window.rootViewController = fakeRoot;
[self.window makeKeyAndVisible];
return YES;
}
That way, you can have all the benefits of inserting the button on the window, without all the guilt and "Should I really be a programmer?" that it causes.
Potentially you could have 1 main "root" view controller, and all you other view controllers could be child view controllers, with their views as child views. Then they would have their content, and the button would be in the "root" view controller. But this seems just as sketchy and hacky as putting it in the window, and probably less convenient.
I use this button:
#interface UIPopUpButton : UIImageView <UIPopoverControllerDelegate, UIActionSheetDelegate>
{
UIPopoverController* popoverController;
Class popoverClass;
}
- (id) initWithPoint: (CGPoint) point;
- (void) touchesBegan: (NSSet*) touches
withEvent: (UIEvent*) event;
+ (id) buttonAtPoint: (CGPoint) point;
+ (id) buttonAtOriginalPoint;
+ (void) unhighlight;
+ (void) bringButtonToFront;
#property (nonatomic, retain) UIPopoverController* popoverController;
#property (nonatomic, assign) Class popoverClass;
#end
#import "UIPopUpButton.h"
#implementation UIPopUpButton
static UIPopUpButton* button = nil;
static CGPoint originalPoint;
#synthesize popoverClass;
#synthesize popoverController;
+ (id) buttonAtPoint: (CGPoint) point
{
if (button == nil)
{
button = [[UIPopUpButton alloc] initWithPoint: point];
originalPoint = point;
button.popoverClass = [UIPopoverController class];
}
else
{
button.frame = CGRectMake(point.x, point.y, button.frame.size.width, button.frame.size.height);
}
return button;
}
+ (id) buttonAtOriginalPoint
{
return [self buttonAtPoint: originalPoint];
}
+ (void) unhighlight
{
button.highlighted = NO;
}
+ (void) bringButtonToFront
{
[[UIApplication sharedApplication].keyWindow addSubview: [self buttonAtOriginalPoint]];
}
- (id) initWithPoint: (CGPoint) point
{
UIImage* image1 = [UIImage imageNamed: #"topbutton.png"];
UIImage* image2 = [UIImage imageNamed: #"topbutton.png"];
if ((self = [super initWithImage: image1
highlightedImage: image2]))
{
self.userInteractionEnabled = YES;
self.frame = CGRectMake(point.x, point.y, self.frame.size.width, self.frame.size.height);
self.multipleTouchEnabled = NO;
}
return self;
}
- (BOOL) isAppCurrStatus
{
return ([DevToolsClientController sharedInstance].statusOfRootViewController == FrontEndApplication);
}
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
if(touch.view == self)
{
if (self.popoverController == nil)
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle: #"Please choice operation:"
delegate: self
cancelButtonTitle: nil
destructiveButtonTitle: nil
otherButtonTitles: nil];
[actionSheet addButtonWithTitle: #"Cancel"];
actionSheet.cancelButtonIndex = 0;
[actionSheet addButtonWithTitle: #"Button 1"];
actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
[actionSheet setTag: 0];
[actionSheet setDelegate: self];
[actionSheet showInView: [self superview]];
[actionSheet release];
[actions release];
}
else
{
PopoverMenuController* contentViewController = [[PopoverMenuController alloc] init];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController: contentViewController];
popoverController.delegate = self;
[popoverController presentPopoverFromRect: CGRectMake(10.0f, 10.0f, 5.0f, 5.0f)
inView: self
permittedArrowDirections: UIPopoverArrowDirectionAny
animated: YES];
contentViewController.popoverController = self.popoverController;
[contentViewController reloadData];
}
}
else
{
[self.popoverController dismissPopoverAnimated:YES];
self.popoverController = nil;
}
}
[super touchesBegan: touches withEvent: event];
}
#pragma mark UIActionSheetDelegate implementation
-(void) actionSheet: (UIActionSheet*) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex
{
NSNumber* indexAction = [[NSNumber alloc] initWithInt: buttonIndex - 1];
}
- (void) runAction: (NSNumber*) indexAction
{
[DevToolsPopoverMenuController runAction: [indexAction integerValue]];
}
#pragma mark -
#pragma mark UIPopoverControllerDelegate implementation
- (void) popoverControllerDidDismissPopover: (UIPopoverController*) thePopoverController
{
if (self.popoverController != nil)
{
self.popoverController = nil;
}
}
- (BOOL) popoverControllerShouldDismissPopover: (UIPopoverController*) thePopoverController
{
//The popover is automatically dismissed if you click outside it, unless you return NO here
return YES;
}
#end
call:
[UIPopUpButton bringButtonToFront];
My button is always on top.
Try subclassing the UIViewController class and make your own one with the button
Create a singleton object that holds the button so all view controllers can reference it and add it to their subview or add it to the window directly.
SomeClass.h
#property (nonatomic) UIButton *yourButton;
+(SomeClass*)sharedSomeClass;
SomeClass.m
#synthesize yourButton = _yourButton;
-(id)init
{
self = [super init];
if(self)
{
_yourButton = [UIButton new];
//Other settings you want for your button
}
return self;
}
+(SomeClass)sharedSomeClass
{
static SomeClass *sharedSomeClass;
if (!sharedSomeClass)
sharedSomeClass = [[super allocWithZone:nil]init];
return sharedSomeClass;
}
+(void)allocWithZone:(NSZone*)zone
{
return [self sharedSomeClass];
}
If you like you can access the window directly like this:
UIWindow *mainwindow = [[[UIApplication sharedApplication]delegate]window];
import SomeClass.h into your view controllers, and access the button from anywhere
#import "SomeClass.h"
SomeClass *someClass = [SomeClass sharedSomeclass];
UIButton *localButton = someClass.yourButton;