Implementing iphone's copy/paste controls on a custom view / uiview subclass - iphone

I will admit that there is already a question exactly along these lines here on S.O., but it lacks implementation details, a working answer, and I would like to be more specific, so I think a new question is in order. Obviously, let me know if I'm wrong and we can try to restart the thread over there.
Basically, I want to copy the text in a UILabel to the pasteboard when a user holds down on the label. Not hard to do, honestly. However, I think the best way to provide visual feedback is to prompt the user with the Copy menu option from UIMenuController.
According to the Event Handling section of the iPhone Application Programming Guide, specifically the section on Copy, Cut, and Paste Operations, it should be possible to provide copy, cut, and/or paste operations from a custom view.
So, I've sub-classed UILabel with the following implementation as described by the guide, but the UIMenuController won't show up. There's no indication in the guide that there's anything else required to do this, and the NSLog statement does print out to the console, indicating that the selector is being performed when I hold down on the label:
//
// CopyLabel.m
// HoldEm
//
// Created by Billy Gray on 1/20/10.
// Copyright 2010 Zetetic LLC. All rights reserved.
//
#import "CopyLabel.h"
#implementation CopyLabel
- (void)showCopyMenu {
NSLog(#"I'm tryin' Ringo, I'm tryin' reeeeal hard.");
// bring up editing menu.
UIMenuController *theMenu = [UIMenuController sharedMenuController];
// do i even need to show a selection? There's really no point for my implementation...
// doing it any way to see if it helps the "not showing up" problem...
CGRect selectionRect = [self frame];
[theMenu setTargetRect:selectionRect inView:self];
[theMenu setMenuVisible:YES animated:YES]; // <-- doesn't show up...
}
// obviously, important to provide this, but whether it's here or not doesn't seem
// to change the fact that the UIMenuController view is not showing up
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
BOOL answer = NO;
if (action == #selector(copy:))
answer = YES;
return answer;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self performSelector:#selector(showCopyMenu) withObject:nil afterDelay:0.8f];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showCopyMenu) object:nil];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showCopyMenu) object:nil];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showCopyMenu) object:nil];
}
#end
So, what else does one have to do to make this happen?
For those following along and trying to do this, too, you'll also need to set 'User Interaction Enabled' for the label
Edit:
For clarity, let me add that this should be similar to the little [Copy] menu item that appears over an image in certain iphone views when you hold down on it. -B

I'll say upfront I don't have an aswer, but I did some poking around and found more out. I'm sure you've looked at this already: CopyPasteTile
That code does work on my simulator and goes like this:
CGRect drawRect = [self rectFromOrigin:currentSelection inset:TILE_INSET];
[self setNeedsDisplayInRect:drawRect];
UIMenuController *theMenu = [UIMenuController sharedMenuController];
[theMenu setTargetRect:drawRect inView:self];
[theMenu setMenuVisible:YES animated:YES];
There are a few differences here:
drawRect is calculated from a giant view tile and tap point calculations
setNeedsDisplayInRect is being called
self is a large screen sized view, you may need screen coords instead of local coords (you can probably get that from self.superview)
I'd try making these adjustments to match the example first and see what kind of progress it gets me.

Related

How to pass touch event to another object?

I referenced the apps called "Comic Strip" and "Balloon Stickies Free"
When i add a speech balloon and touch s.b or s.b's tail, it works. But when i touch tail's around or between s.b and s.b's tail, it doesn't work. And Photo gets touch and works below the s.b.
So i tried to use hitTest:withEvent.
It works when i touch rectangle or tailRect first time. But when i touch other place in the object, and i touch rectangle or tailRect again, it doesn't work.
So how to modify this code ? i don't know .. help me please
- (id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
if(CGRectContainsPoint(rectangle, currentPt)==YES || CGRectContainsPoint(tailRect, currentPt)==YES)
return hitView;
else
return nil;
}
Try overriding - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event instead.
Or take a look at Ole Begemann's OBShapedButton. Code can easily be modified
to work with UIView instead of UIButton.
See this post horizontal scrolling. Using this code you can get all touch events in a single UIWindow class. You have to write some code to pass control appropriately.

Custom UIButton drawing - stays depressed after touchesEnded

I've made a custom zero-image, Core Graphics drawn UIButton based on Ray Wenderlich's Tutorial (the only modifications are to CoolButton's drawRect: method, altered drawing code), and it works great most of the time. However, sometimes when I click it for a short period of time, it stays in a depressed state and doesn't return to normal.
From here, the only way to get it back to a normal state is via a long press. Simply clicking means it stays depressed.
Another thing to note is that I've hooked Touch Up Inside up to a chain of a few long methods - I don't think it would take more than 0.1 seconds to complete. I've even used dispatch_async in the #selector that is hooked up to Touch Up Inside, so there shouldn't be a delay in the UI updating, I think.
I've put an NSLog in the drawRect: which fires 3 times per button press usually, and it varies what UIControlState the button is in for each press:
For some short presses, it goes Highlighted, Highlighted, Normal
for longer presses, it's Highlighted, Normal, Normal
However, for very short presses, it only fires twice, Highlighted -> Highlighted.
When it's a long press to get it back to Normal, it goes H, N, N.
This has been puzzling me for a while, and I haven't been able to work out why short presses only fire drawRect: twice, or why touchesEnded: doesn't seem to call drawRect:. Perhaps touchesEnded: isn't firing?
I really hope someone can help.
If you really want to generate the button images at runtime, generate them when the button is loaded. Then add them for different states using
[button setImage:btnImage forState:UIControlStateNormal];
You can always turn a view into an image using the following: http://pastie.org/244916
Really though, I'd recommend just making images beforehand. If you don't want to get photoshop, there's plenty of alternatives. The upcoming pixelmator update looks pretty suave, ands it's ridiculously cheap!
Well, well, that was easy. 0.15 second delay works, 0.1 doesn't.
As other said, you can simply generate an image and use that as background (if the image is static). No need photoshop, you may simply generate your image once and then take a snapshot, then cut the image out (with Anteprima) and save it as a png file :)
However, since you said the button is connected to some long methods, this may be why the button stay pressed: if you are not calling those methods in background, then the button will stay pressed since all the task are ended. Try (for a test) to connect the button with a single simple method (say NSLog something) and check if it stay pressed. If not, I suggest to detach your methods in background.
I too ran into a similar problem with a UIButton appearing to stick one state when I customize the drawRect method. My hunch is that the state of the button is changing more than once before drawRect is called. So I just added a custom property that would only be set by methods called during touchDown and touchUpInside events. I had to dispatch threads to do this because of other operations that were holding up the main thread, causing a delay in the redrawHighlighted method.
It may be a little messy, but here's what worked for me:
// CustomButton.h
#interface CustomButton : UIButton
#property (atomic) BOOL drawHighlighted;
// CustomButton.m
#implementation CustomButton
#synthesize drawHighlighted = _drawHighlighted;
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
_drawHighlighted = NO;
[self addTarget:self action:#selector(redrawHighlighted) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:#selector(redrawNormal) forControlEvents:UIControlEventTouchUpInside];
[self addTarget:self action:#selector(redrawNormal) forControlEvents:UIControlEventTouchDragExit];
}
return self;
}
- (void)drawRect:(CGRect)frame
{
// draw button here
}
- (void)redrawNormal
{
[NSThread detachNewThreadSelector:#selector(redrawRectForNormal) toTarget:self withObject:nil];
}
- (void)redrawHighlighted
{
[NSThread detachNewThreadSelector:#selector(redrawRectForHighlighted) toTarget:self withObject:nil];
}
- (void)redrawRectForNormal
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
_drawHighlighted = NO;
[self setNeedsDisplay];
[pool release];
}
- (void)redrawRectForHighlighted
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
_drawHighlighted = YES;
[self setNeedsDisplay];
[pool release];
}
Another cheap but effective alternative I've found is to create a drawRect: that doesn't do any highlighting, and simply alter the button subclass's alpha property when the user interacts with the button.
For example:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
self.alpha = 0.3f;
return [super beginTrackingWithTouch:touch withEvent:event];
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
self.alpha = 1.0f;
[super endTrackingWithTouch:touch withEvent:event];
}
- (void)cancelTrackingWithEvent:(UIEvent *)event
{
self.alpha = 0.3f;
[super cancelTrackingWithEvent:event];
}
- (void)drawRect:(CGRect)rect
{
// Your drawing code here sans highlighting
}

UIImageView scope. Accessing from another class

Here's part of the code I'm working with: http://pastie.org/2472364
I've figured out how to access the UIImageView from another method within the same class file in which it was programmatically created.
However, I was wondering how I'd access that same UIImageView from within the LetterTiles.m file, specifically within the touchesMoved method. The way I wrote the code in the sample, it will only show if the frames intersect if they're on top of each other when the otherMethod is called. Of course, I need to be able to check if the views intersect within the actual touchesMoved method. I'm sure it's something super easy, but I'm just not sure how to do it.
Thanks in advance for any help you can give me.
From your comment, and using the code you already have, I would go down this route. This isn't what I would do personally, just FYI. The structure is a bit shakey with the way it sounds like you want this.
Create the place holder UIImageView in the touchesBegan function, then check to see if they intersect when the user stops moving the image.
#import "LetterTiles.h"
#implementation LetterTiles
#synthesize placeHolder;
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
// Retrieve the touch point (I consider this useful info to have, so I left it in)
CGPoint pt = [[touches anyObject] locationInView:self];
startLocation = pt;
// Create a place holder image wherever you want
[self setPlaceHolder:[[[UIImageView alloc] initWithFrame:CGRectMake(39, 104, 70, 70)] autorelease]];
[newImage setImage[UIImage imageNamed:#"placeHolder.png"]] autorelease];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint pt = [[touches anyObject] locationInView:[self superview]];
[self setCenterPoint:pt];
}
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
LetterTiles *movingTile = self;
if (CGRectIntersectsRect([movingTile frame], [placeHolder frame])) {
NSLog(#"Touched");
[self setFrame:[placeHolder frame]];
}
}
Make a protocol called ViewMoved which will contain one method otherMethod.
implement that in myMainViewController
take a delegate property of type ViewMoved in LetterTiles.
Assign self when you make new object of type LetterTiles in myMainViewController.
On every movement of touch call oherMethod of delegate and check whether any views of type LetterTiles are intersecting or not.
This will catch any intersection when any of the view is moved.....
If above is not matching with your question then write here......

Putting UIButton and other UIControl objects inside an MKAnnotationView and allowing user interaction

I have a custom annotation view on the map, which has a UIButton in it, but the UIButton is not responsive when pressed. I have two main problems with user interaction on the annotation view:
Buttons and other controls are not responsive.
I want the annotation to block touches according to my implementation of - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event - that is if I return YES then I don't want the touches to get sent through to the MKMapView (potentially selecting other annotations that are BEHIND my annotation view), I want to handle the touch myself in this case.
I have made sure userInteractionEnabled is set to YES and I have investigated how touches are sent to the custom annotation view (my subclass of MKAnnotationView) by overriding touchesBegan etc. - but it appears that the touches are usually cancelled (thought I've managed to get touchesEnded a few times) - so it seems like it will even be difficult to manually implement any user-interaction with the custom annotation view.
Does anyone have any insights into allowing more user interaction with MKAnnotationView objects?
I managed to resolve this with the help of a colleague. The solution is to override - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event. Our assumption is that MKAnnotationView (which your annotation view must inherit from) overrides this to do the 'wrong' thing (presumably so that annotation selection doesn't get blocked between overlapping annotations). So you have to re-override it to do the right thing and return the appropriate UIView, the system will then send the events to it and the user will be able to interact with it :). This has the beneficial (in this case) side-effect that the interactive annotation blocks the selection of annotations that are behind it.
I found that rather than overriding hitTest:withEvent: I could just override pointInside:withEvent: instead and just get it to return YES. I guess that officially I should be doing a point-rect intersect check to ensure the place I'm tapping is within the control element, but in practise, just putting return YES appears to work perfectly well, still allowing you to dismiss the MKAnnotationView by tapping away from it.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// for testing purposes
BOOL result = [super pointInside:point withEvent:event];
NSLog(#"pointInside:RESULT = %i", result);
return YES;
}
Adding up to the answer of jhabbott, this is what worked for me. I have a custom annotation view MKCustomAnnotationView that holds a custom annotation CustomPin as annotation. That 'pin' holds a UIButton as accessory button replacement which I wanted to get touch events.
My hitTest method would look like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *result = [super hitTest:point withEvent:event];
//NSLog(#"ht: %f:%f %d %#", point.x, point.y, [[event touchesForView:self] count], result);
if ([result isKindOfClass:[MKCustomAnnotationView class]])
{
MKCustomAnnotationView *av = (MKCustomAnnotationView *)result;
CustomPin *p = av.annotation;
UIButton *ab = p.accessoryButton;
if (p.calloutActive && point.x >= ab.frame.origin.x)
return ab;
}
return result;
}
The calloutActive bool is probably not necessary in most cases.
For anyone looking to add a tapGesture to an AnnotationView subview then the answer at the bottom of this:
MKannotationView with UIButton as subview, button don't respond
Worked for me:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(_button.frame, point)) {
return _button;
}
return [super hitTest:point withEvent:event];
}

action won't run on a CCSprite from within ccTouchBegan in cocos2d for iphone

I'm trying to basically scale up a button as soon as a touch is detected. Here's my Scene:
#implementation HomeScene
-(id) init
{
if((self = [super init])) {
...
// sp_btn_story is retained...
sp_btn_story = [[CCSprite spriteWithFile:#"main_menu_btn.png"] retain];
sp_btn_story.position = ccp(size.width - 146, 110);
[self addChild: sp_btn_story];
...
}
return self;
}
-(void) onEnter
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
-(void) onExit
{
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
NSLog(#"tapped!");
sp_btn_story.scaleX = 2;
[sp_btn_story stopAllActions];
[sp_btn_story runAction: [CCScaleTo actionWithDuration:0.5f scale:1.2f]];
return YES;
}
...
#end
It scales the X just fine, as expected. (I threw that in there to test.) But the action is not running for some reason. :( Anyone have any ideas?
Edit: using cocos2d 0.99 btw.
The answer for this question (reposted from the asker's own comment to make it more visible):
It's quite important to call
[super onEnter];
if you overwrite this method in a subclass or strange things may happen. Cocos2D will not be happy.
This applies to other (if not all) methods as well, e. g. always call
[super onExit];
as well.
Not complete sure what could be going on. Looks like what you have should work fine but you may want to try applying a different action to sp_btn_story like fade in or fade out to see if at least any action will work. Failing that you could also try applying the action in a different part of your code. These are not solutions but they may provide evidence to indicate what exactly is going on.