UIWebView canPerformAction do not disable some menu items - ios

In a UIWebView, I want a certain class div element to display only one custom contextual menu entry. So that I implemented the canPerformAction:: method in the UIWebView delegate like this:
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (self.webView.superview != nil) {
BOOL isMyClass=[[self.webView stringByEvaluatingJavaScriptFromString:#"window.getSelection().getRangeAt(0).startContainer.parentNode.className;"] isEqualToString:#"myClass"];
if (isMyClass) {
if (action == #selector(myAction:)) {
return YES;
} else {
return NO; // should disable any other menu items
}
}
}
return [super canPerformAction:action withSender:sender];
}
The result is quite strange: when the user selects such a myclass div, most menuItems are not displayed (cut: copy: past:...) but select: and selectAll: are still displayed (along with myAction). Under debugger, I notice that these two select/selectAll methods do not fire canPerformAction:: in the delegate... Where are these two method fired?

I think I know why you may be having problems.
I had the same question and similar frustration:
"Why are select: and selectAll: not appearing when stepping through calls to canPerformAction::?"
I then realized that the firstResponder when displaying the UIMenuController was just a container, and that this class had a member that was actually extending the UITextView class. Since the sharedMenuController interacts with the first responder in the Responder chain, implementing canPerformAction in the container skipped select and selectAll because they had already been handled by the textView member (the REAL firstResponder in this situation).
What you should do is find which object is your firstResponder when displaying the UIMenuController, find any responder objects it might own until you find the highest responder on the stack, and implement canPerformAction there.
Good Luck!

Sometimes, when application is used on the iPad device, with no connection to Xcode, the menu correctly displays only the authorized item... Sometimes not... Very erratic behaviour indeed

Related

No paste: action in canPerformAction:withSender:

I am trying to forcibly hide the Paste bubble on my UITextField.
My implementation is to specify a list of prohibited selectors from UIResponderStandardEditActions, store it in an AssociatedValue in a UIResponder category and quit category's canPerformAction:withSender: prematurely if action is found in the list. This is quite tempting approach, because it lets to control any Responder in the project.
Problem is no paste: action reaches any canPerformAction:withSender: method for the whole responder chain when I tap inside my UITextField. I wrote a category on UIResponder and swizzled canPerformAction:withSender: there, so I can be sure:
- (BOOL)my_canPerformAction:(SEL)action withSender:(id)sender {
NSString *string = NSStringFromSelector(action);
BOOL prohibited = [self.prohibitedActions containsObject:string];
if (prohibited) {
return NO;
}
BOOL canPerform = [self my_canPerformAction:action withSender:sender];
return canPerform;
}
The whole catch for my hierarchy is:
cut:
copy:
select:
selectAll:
delete:
_promptForReplace:
_transliterateChinese:
_insertDrawing:
_showTextStyleOptions:
_lookup:
_define:
_define:
_addShortcut:
_accessibilitySpeak:
_accessibilitySpeakLanguageSelection:
_accessibilityPauseSpeaking:
_share:
makeTextWritingDirectionLeftToRight:
Prohibiting _promptForReplace: does not help. Also, my TextField does not implement canPerformAction:withSender:.
So, what should I do to track down and hide that nasty paste?
So in swift I will do like this:
UIMenuController.shared.menuItems?.removeAll(where: {$0.title == "paste"})
In objective-c you can try something like this:
UIMenuController * controller = [UIMenuController sharedMenuController];
NSArray * items = [controller menuItems]; // These are all custom items you added
NSMutableArray * finalItemsYouWant = [NSMutableArray array];
// Here you can check what items you don't want and then remove it
[controller setMenuItems:finalItemsYouWant];
So try finding out all the menu items and forcefully remove the one you want
Creating category on UITextField instead of UIResponder did the trick.
Subclassing UITextField and implementing canPerformAction:withSender: works either.
It turned out that category on UIResponder does not affect canPerformAction:withSender: on UITextField, even though UITextField IS-A UIResponder. I do not know whether it is a bug in iOS or some oddity in it's internal behavior.
My fault was to rely too much on swizzling. I do not recommend you this "universal" approach like making a category with a list of "prohibited" action selectors to work with any responder.

Detecting multiple touches WKInterfaceTable

Is there a way to detect if a table row has already been selected, right now I am protecting against multiple pushes with a boolean like so:
- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
pushed=NO;
}
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex {
// if pushed, just return else continue and set pushed to true
if (pushed) {
return;
}
pushed=YES;
[self pushControllerWithName:rowData[#"controllerIdentifier"] context:nil];
}
There is no built-in method to detect multiple touches in a WKInterfaceTable. The technique that you're using is the same technique that I use in my Watch app. In my case, I maintain a BOOL for each row indicating whether it's enabled or not. Based on that BOOL, I updated my cell to show a "disabled"-looking state, though of course, it isn't technically disabled.

Return to root view in IOS

To some this may sound like a daft question. I've searched around, and found little, mostly because I cannot find the appropriate search terms.
Here what I want to do is:
The application begins at view A.
View A launches view B, and view B launches view C.
Is their a way for view C to return directly back to A without dismissing itself and thus exposing B. For example a main menu button.
You can call popToRootViewControllerAnimated: if you have a UINavigationController. If you specify NO to animate it, then it will just jump back to the root without showing B first.
I have discovered a solution to my problem. Its a bit dirty, (and I''ll probably get shot down in flames for it) but works very well under tests and is very quick to implement. Here's how I did it.
In my app I have a Singleton class called GlobalVars (I use this for storing various global settings). This class holds a boolean called home_pressed and associated accessors (via synthesise). You could also store this value in the application delegate if you wish.
In every view controller with a main menu button, I wire the button to the homePressed IBAction method as follows. First setting the global homePressed boolean to YES, then dismissing the view controller in the usual way, but with NO animation.
-(IBAction) homePressed: (id) sender
{
[GlobalVars _instance].homePressed = YES;
[self dismissModalViewControllerAnimated: NO];
}//end homePressed
In every view controller except the main menu I implement the viewDidAppear method (which gets called when a view re-appears) as follows.
-(void) viewDidAppear: (Bool) animated
{
if ([GlobalVars _instance].homePressed == YES)
{
[self dismissModalViewController: NO];
}
else
{
//put normal view did appear code here/
}
}//end viewDidAppead
In the mainMenu view controller which is the root of the app, I set the global homePressed boolean to NO in its view did appear method as follows
-(void) viewDidAppear: (Bool) animated
{
if ([GlobalVars _instance].homePressed == YES)
{
[GlobalVars _instance].homePressed == NO;
}
else
{
//put normal view did appear code here/
}
}//end viewDidAppear
There, this enables me to go back to the root main menu of my app from any view further down the chain.
I was hoping to avoid this method, but its better than re-implementing my app which is what I'd have to do if I wanted use the UINavigationController solution.
Simple, took me 10 minutes to code in my 9 view app. :)
One final question I do have, would my solution be OK with the HIG?

Disable action items in UIDocumentInteractionController's presentPreviewAnimated

I am using UIDocumentInteractionController's presentPreviewAnimated method to preview document. It works fine. But I wish to disable the action button while in the preview mode. I have the following two delegated methods to return NO. But these two methods never got called at all. The other delegated methods work fine. Any suggestion?
-(BOOL)documentInteractionController:(UIDocumentInteractionController *)controller canPerformAction:(SEL)action {
NSLog(#"canPerformAction");
return NO;
}
and
-(BOOL)documentInteractionController:(UIDocumentInteractionController *)controller performAction:(SEL)action {
NSLog(#"performAction");
return NO;
}
I finally switched to use the QLPreviewController class. Where you can subclass it and make the action button gone away. (With the help from the answer by rbrown of this SO.

Using the same View Controller for add, display, and edit

I am working on an iOS app that uses a very common Core Data based tableview to display items and when one it selected, it shows a more detailed view, much like the Contacts app. The detail view itself is a programmatically generated grouped table with a custom (nib-defined) view for a header that has a picture and a name. Some of the cells in the table are custom cells that have a label name and a textbox value. In "edit" mode, the editable table cells (and the name in the header) have .clearButtonMode set to UITextFieldViewModeAlways to show that they are editable.
I am currently using the same view controller to display the detailed information, edit the information, and add a new record to the original list.
When a new item is being added, the view controller is created modally with a custom init overload that sets a flag in the view controller to indicate that it is adding the record. This allows it to start in edit mode and if edit mode is left, the model view is dropped away. The right menubar button is the usual Edit/Done, and the left one is a cancel button. When an existing item is being edited, the left button (normal back button) is replaced with a cancel button.
I am starting to have second thoughts as to whether or not having one view controller handle three different modes is the way to go. There are few issues that I am not sure how to handle.
1) How do I tell if edit mode is left by hitting "Done"? Is there an action for it? If cancel is hit, the action either dismisses itself (add mode) or restores the previous values leaves edit mode. I suppose I could put a check in my setEditing override to handle it, but it seems that there should be a better way.
2) When edit mode is entered and I set the editable text fields to UITextFieldViewModeAlways, is there a way to animate the appearance of the 'X' buttons so that they fade in with the editing indicators on the regular cells?
Are there easy solutions to these problems or is my 3-in-1 view controller a bad idea? It doesn't seem right to remake the same view for different modes, but having multiple modes for a view controller seems to be a bit of a hassle.
jorj
I like the 3-in-1 approach and use it all the time. There are lots of advantages: one xib, one view controller, one simple protocol between the list and detail view controllers. Yes, there are a few more checks like if (self.editing) ... but I like that better than more view controllers and xibs.
To help with adding I expose a BOOL that the delegate can set.
#property (nonatomic) BOOL adding;
1) The built-in editButtonItem does not allow you to intercept it before setEditing:animated: This is problematic when you are doing data validation after Done is tapped. For that reason I rarely use editButtonItem and use my own Edit, Done, and Cancel buttons with their own action methods. See below.
2) For this I like UITableView's reloadSections:withRowAnimation. It might work in your case.
- (void)edit:(id)sender
{
self.editing = YES;
}
- (void)done:(id)sender
{
// data validation here
if (everythingChecksOut)
{
//save here
} else {
return; //something didn't validate
}
//if control reaches here all is good
//let the delegate know what happened...
if (self.adding) {
[self.delegate didFinishAddingWithData:self.yourData];
} else {
[self.delegate didFinishEditingWithData:self.yourData];
}
self.adding = NO;
self.editing = NO;
}
- (void)cancel:(id)sender
{
[self.view endEditing:YES]; //in theory, forces the view that is editing to resign first responder
//in practise I find it doesn't work with the YES parameter and I have to use my own flag
// roll back any changes here
self.editing = NO;
if (self.adding) //let the delegate know we cancelled the add...
{
[self.delegate didCancelAdd];
}
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
//set your nav bar title
[self.tableview.editing = editing]; //you may or may not require this
[self.tableview reloadSections... withRowAnimation:yourChoice];
if (editing)
{
//install your Done and Cancel buttons
} else {
//remove Cancel and put the Edit button back
}
}
Then in viewDidLoad...
- (void)viewDidLoad
{
[super viewDidLoad];
//whatever else you do
if (self.adding)
{
self.editing = YES;
}
}
I haven't fully understood the questions you have raised, but here are some thoughts on structure which are probably more useful in the first instance...
It seems you are doing too much with a single UITableViewController and inevitably you will end up with lots of if statements and confusing code. I'd break it down into two separate UITableViewControllers, one to handle the main view (and any subsequent editing mode you require) and then another to handle the detail view. Either or both of these could then use nibs as you require.
Using two controllers like this will allow you to simply push the second detailViewController onto a navigation stack, rather than presenting it modally which doesn't seem like the obvious thing to do in this instance.
However, if you would prefer it to be presented modally, you could write a protocol for the detailView which sends messages in the event of 'Cancel', 'Edit' or 'Done' buttons being pushed. The first viewController could then implement the protocol and receive these events.
I hope that helps...

Resources