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.
Related
So I am implementing a custom navigation item in my view controller via the method like this
-(UINavigationItem*)navigationItem{
item = [[SearchNavigationItem alloc] init];
item.delegate = self;
return item;
}
The SearchNavigationItem will set itself up, add a UITextField and so on.
The field.delegate will have the item as the delegate.
So the issue I have is that when I try to grab the text of the field, it is nil. But when the "textfield changed" is called, I can access the field via the argument (textFieldDidChange:UITextField*) and it has the text.
Another issue, like the title, was that when I did [field resignFirstResponder] nothing happened.
Okay so I already have the answer, and I am writing this question because I could personally not find any help while fixing it.
So the issue is that navigationItem can be called multiple times and this kept creating new bars.
So the solution became, simply, this:
-(UINavigationItem*)navigationItem{
// Apparently it should be treated as a 'singleton' which I think it says
// kind of in the documentation. This comment is just to reinforce that
// it burned me to init it each time this method is called. Which is can
// be multiple times and also outside of the class itself (like when nav'ing)
if(item == nil){
item = [[SearchNavigationItem alloc] init];
item.delegate = self;
}
return item;
}
Hope this helps someone else.
So I have this BaseCell class which also has this BaseCellViewModel. Of course on top of this lives some FancyViewController with FancyViewModel. The case here is that BaseCell has UIButton on it which triggers this IBAction method - that's fine and that's cool as I can do whatever I want there, but... I have no idea how should I let know FacyViewController about the fact that some action happened on BaseCell.
I can RACObserve a property in FancViewModel as it has NSArray of those cell view models, but how to monitor actual action and notify about exact action triggered on cell?
First thing that came to my mind is the delegation or notifications, but since we have RAC in our project it would be totally stupid not to use it, right?
[Edit] What I did so far...
So, it turns out youc can use RACCommand to actually handle UI events on specific button. In that case I've added:
#property (strong, nonatomic) RACCommand *showAction;
to my BaseCellViewModel with simple implementation like:
- (RACCommand *)showAction {
return [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(#"TEST");
return [[RACSignal empty] logAll];
}];
}
And following this pattern I had to do something in my BaseCell which turned out to be quite simple and I ended up with adding:
- (void)configureWithViewModel:(JDLBasePostCellViewModel *)viewModel {
self.viewModel = viewModel;
self.actionButton.rac_command = self.viewModel.showAction;
}
And... It works! But...
I need to present UIActionSheet whenever this happens and this can be show only when I need the current parentViewController and since I don't have this kind of information passed anywhere I don't know what to do right now.
FancyViewModel holds a private #property (nonatomic, strong) NSMutableArray <BaseCellViewModel *> *cellViewModels;, but how can I register something on FancyViewController to actually listen for execution of RACCommand on BaseCellViewModel?
There are a few ways that the cell might communicate with the view controller. A common on is via delegation. Have the cell declare a public delegate, like:
// BaseCell.h
#protocol BaseCellDelegate;
#interface BaseCell : UITableViewCell
#property(nonatomic, weak) id<BaseCellDelegate> delegate;
// ...
#end
#protocol BaseCellDelegate <NSObject>
- (void)baseCell:(BaseCell *)cell didReceiveAction:(NSString *)actionName;
#end
When the button is pressed, work out what you'd like to tell the delegate, and tell it:
// BaseCell.m
- (IBAction)buttonWasPressed:(id)sender {
self.delegate baseCell:self didReceiveAction:#"someAction";
}
Then, in the view controller, declare that you conform to the protocol:
// FancyViewController.m
#interface FancyViewController () <BaseCellDelegate>
in cellForRowAtIndexPath, set the cell's delegate:
// dequeue, etc
cell.delegate = self;
You'll now be required to implement this in the vc:
- (void)baseCell:(BaseCell *)cell didReceiveAction:(NSString *)actionName {
// the cell got an action, but at what index path?
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// now we can look up our model at self.model[indexPath.row]
}
UITableViewCell are volatile, reusable components. They come and go, and your button will do as well.
How about following #danh suggestion and once control is in View Controller, formulate a RAC signal programmatically.
Since I feel rather belonging to RxSwift camp :) I cannot provide source snippet, but this answer is probably what I meant.
As you already have a RACCommand in BaseCellViewModel, you can use one of its convenience signals. For example, you can track its state using executing signal:
[baseCellViewModel.showAction.executing subscribeNext:^(NSNumber *executing) {
//do something if the command is executing
}];
Bindings with RACObserve will work as well, if you need them.
You can also get the latest value from the command's underlying signal (but in the code you posted it won't work, as you use [RACSignal empty] with doesn't send any 'next' values):
[[baseCellViewModel.command.executionSignals switchToLatest] subscribeNext:^(id x) {
//do something with the value
}];
Note that you should subscribe to this signals when you create BaseCellViewModel, not in configureWithViewModel as the latter will be called many times (resulting in many subscriptions for the same signal).
I am trying to implement a feature in iOS project that when you select a piece of text and highlight it you can then choose from the menu options to use another app like the default dictionary. Is it possible to do this? If so where can I find such documentation or tutorials?
You are describing the iOS menu. Look at the documentation on classes such as UIMenu, UIMenuItem, and UIMenuController.
I've found a solution to my problem.
Thanks to the author of this article:
http://blog.studiovillegas.com/2014/02/06/ios-uipasteboard-uimenucontroller-and-uimenuitem/
To add a custom menu item on to the default menu controller.
ViewController.h
- (void)longPressGestureRecognizer:(UIGestureRecognizer *)recognizer
{
UIMenuItem *mi = [self.label menuItemOpenPleco];
UIMenuController *menuController = [UIMenuController sharedMenuController];
menuController.menuItems = #[mi];
}
PasteboardLabel {h,m}
#interface PasteboardLabel : UILabel
- (UIMenuItem *)menuItemOpenPleco;
#end
#implementation PasteboardLabel
- (UIMenuItem *)menuItemOpenPleco
{
return [[UIMenuItem alloc] initWithTitle:#"Open Pleco" action:#selector(openPleco:)];
}
- (void)openPleco:(id)sender
{
NSString *selectedText = [self textInRange:[self selectedTextRange]];
UIPasteboard *pb = [UIPasteboard generalPasteboard];
pb.string = selectedText;
NSString *urlString = [NSString stringWithFormat:#"plecoapi://x-callback-url/q?s=%#", pb.string];
NSURL *url = [[NSURL alloc] initWithString:urlString];
[[UIApplication sharedApplication] openURL:url];
}
#end
I've found that there's a dearth of examples of adding custom menu items, or explanations of how they work. So I wanted to resolve that by sharing a few important tidbits then showing an example.
The UIMenuController "talks" with UIViews, not with UIViewControllers. This means that your UIMenuController related code needs to go into subclasses of UIView rather than a UIViewController.
Notice the word The at the start of my prior example. There's only one UIMenuController, a singleton which is shared from when your application first starts until it ends. This means that you should only add your item once, and that you shouldn't be writing over the existing array of items.
The appearance of the button in the UIMenu is based on whether or not the UIView that was tapped responds to the selector. This means you need to implement the method if you want the button to appear, and that you don't need to worry about it appearing when unrelated views are tapped unless you pick a selector name for which other UIViews also have methods.
So, having said all that, I made a subclass of a UITextView (which means its a subclass of UIView per my first bullet) and then I gave it this initialize method, along with an implementation for my selector.
+ (void)initialize {
static dispatch_once_t addInsert;
dispatch_once(&addInsert, ^{
UIMenuController *mController = [UIMenuController sharedMenuController];
UIMenuItem *insert = [[UIMenuItem alloc] initWithTitle:#"Insert..."
action:#selector(insert:)];
mController.menuItems = [mController.menuItems arrayByAddingObject:insert];
});
}
- (void)insert:(id)sender {
NSLog(#"Insert... pressed!");
}
The important points above here:
It's in the class initialize method, which is called by the runtime before the first time any other method in your class is invoked. In practice means the code is handled just before the first time an instance of your custom view will be appearing on screen.
I added a dispatch_once guard around it. If my class is subclassed, it's possible that those subclasses will call this initialize method. Maybe those subclasses show up before this one does, so I don't want to prevent the initialize method from running then. I just want to prevent it from running multiple times. Thus why I wrapped the code in a dispatch_once.
I didn't just set the menuItems to a new array of items - I assigned it to a new array of items that extended the existing array of items with my new item.
Hope you find all of that helpful. It's not very complicated, and you can certainly go about implementing my second point in other ways - I tried to pick a way that seemed safest to me, but there are certainly simpler ways of doing it.
I want to implement a custom subclass of UIControl. It works beautifully except for one fatal problem that is making me spit teeth. Whenever I use sendActionsForControlEvents: to send an action message out, it omits to include a UIEvent. For example, if I link it to a method with the following signature:
- (IBAction) controlTouched:(id)sender withEvent:(UIEvent *)event
... the event always comes back as nil! The problem seems to occur within sendActionsForControlEvents:
Now, I need my IBAction to be able to determine the location of the touch. I usually do so by extracting the touches from the event. Surely there must be some way to ensure that the correct event is delivered? It's a pretty fundamental part of using a UIControl!
Anyone know a legal workaround?
I would assume that this is because the sendActionsForControlEvents: method can't know which UIEvent (if any) your control event should be associated with.
You could try to send all the actions separately (replicating what the sendActionsForControlEvents: method does, according to the documentation), so you can specifically associate them with a UIEvent:
UIEvent *event = ...;
UIControlEvents controlEvent = ...;
for (id target in [self allTargets]) {
NSArray *actions = [self actionsForTarget:target forControlEvent:controlEvent];
for (NSString *action in actions) {
[self sendAction:NSSelectorFromString(action) to:target forEvent:event];
}
}
I have ONE possible solution at the moment, but I'm not very happy about it. For others faced with the same problem though, here it is. First, declare a local variable or property for a UIEvent thus:
#property (nonatomic, assign) UIEvent * currentEvent;
Now, in your touch-handling routines, set that local variable to the current UIEvent for that routine before calling [self sendActionsForControlEvents:] like so, replacing UIControlEventTouchDown with whichever action you want to send out of course.
self.currentEvent = event;
[self sendActionsForControlEvents: UIControlEventTouchDown];
Finally, override the following method thus:
- (void) sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
[super sendAction:action to:target forEvent:self.currentEvent];
}
This works, but I am not in the least bit fond of it, so if anybody has an alternative solution that doesn't rely on holding a weak reference to a UIEvent, I will be overjoyed to hear it!
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