I'm trying to find a simple way to include a hyperlink within the text of a label in my iOS app. The goal is to have the user tap the URL and the app will open a Safari browser with that URL.
I've read about including a button with the URL as the label, but that's not going to work for my application.
Is there a simple way to do this?
Thanks so much
You can achieve this by using NSArrtibutedStrings — but I would recommend to use some wrapper around this C-functions. I like OHAttributedLabel.
The demo included shows exactly, how hyperlinks can be handled.
Instead of calling Safari you could start a UIWebView. You have more control about the actions the user can do at that web page.
You need to enable user interactions for your label and then override the - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event method to handle the touch.
To enable user interactions set the following property on your UILabel:
urlLabel.userInteractionEnabled = YES;
An example of touchedEnded:WihEvent: in your UIViewController:
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch;
CGPoint currPt;
if ((touches = [event touchesForView:urlLabel]))
{
touch = [touches anyObject];
currPt = [touch locationInView:self.view];
if ( (currPt.x >= urlLabel.frame.origin.x) &&
(currPt.y >= urlLabel.frame.origin.y) &&
(currPt.x <= (urlLabel.frame.origin.x + urlLabel.frame.size.width)) &&
(currPt.y <= (urlLabel.frame.origin.y + urlLabel.frame.size.height)) )
{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlLabel.text]];
return;
};
};
return;
}
Related
I have a UIWebView and I want to detect any touch on that. (I don't want to use UITapGsture or any other thing)
I am using sendEvent: method of UIApplication for this purpose
and check if touch object contains webview.
Surprisingly it points to UIWebBrowserView. I have to check it's superview to get browser but it makes my code very inefficient because sendEvent is called every time when user makes a tap.
Code Snippet :
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
NSSet *touches = [event allTouches];
if (touches.count != 1)
return;
UITouch *touch = touches.anyObject
if([touch.view isKindOfClass:[UIWebView class]]){ // This fails
}
}
I want to know is there a way to make UITouch return WebView as an object instead of returning it's child views like UIPdfView or UIWebBrowser view?
Finally I found a solution to this :
We have to use :
if([touch.view isMemberOfClass:[UIWebView class]]){ // This Works
}
instead of :
if([touch.view isKindOfClass:[UIWebView class]]){ // This fails
}
We want to count number of shakes done by user.
We tried motionBegan, motionEnded but its of no use.
Because they are fired only when user start a shake or ends a shake but i want to count shakes continuously.
May be something like this, when user moves iPhone to left one side and right one side, I count it as one shake.
Any help would be appreciated.
Thanks
You could use the UIAccelerometer to achieve what you want.
You use motionBegin to detect a start :
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
UIAccelerometer* acc = [UIAccelorometer sharedAccelerometer];
acc.delegate = self;
acc.updateInterval = /* whatever you feel like OK */ 0.1;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
UIAccelerometer* acc = [UIAccelorometer sharedAccelerometer];
acc.delegate = nil;
}
and in the delegate method :
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
//
// You detect a full shake
//
}
Before implementing this, you should know that UIAccelerometer has been deprecated in iOS5. This means you will have to use what Apple recommands to use instead. I'm not updated yet on the topic.
Here is the documentation about it : link.
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.
I can't find anything to explain lost UITouch events. If you smash your full hand on the screen enough times, the number of touchesBegan will be different than the number of touchesEnded! I think the only way to actually know about these orphaned touches will be to reference them myself and keep track of how long they haven't moved.
Sample code:
int touchesStarted = 0;
int touchesFinished = 0;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
touchesStarted += touches.count;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
touchesFinished += touches.count;
NSLog(#"%d / %d", touchesStarted, touchesFinished);
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
Don't forget about touchesCancelled: UIResponder reference
Editing in response to the poster's update:
Each touch object provides what phase it is in:
typedef enum {
UITouchPhaseBegan,
UITouchPhaseMoved,
UITouchPhaseStationary,
UITouchPhaseEnded,
UITouchPhaseCancelled,
} UITouchPhase;
I believe that if a touch starts and ends in the same touch event set, -touchesBegan:withEvent: will be called but will contain touches which have ended or cancelled.
You should change your counting code, then, to look like this:
int touchesStarted = 0;
int touchesFinished = 0;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self customTouchHandler:touches];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self customTouchHandler:touches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self customTouchHandler:touches];
}
- (void)customTouchHandler:(NSSet *)touches
{
for(UITouch* touch in touches){
if(touch.phase == UITouchPhaseBegan)
touchesStarted++;
if(touch.phase == UITouchPhaseEnded || touch.phase == UITouchPhaseCancelled)
touchesFinished++;
}
NSLog(#"%d / %d", touchesStarted, touchesFinished);
}
Every single touch event will go through both phases of started and finished/cancelled, and so your counts should match up as soon as your fingers are off the screen.
One thing to remember... You may receive some touches that have multiple taps. Don't forget to take tapCount into account.
However, if you still have the problem, you can consider all touches from the event, though it presents some other management issues...
HACK ALERT
I have coded the following HACK to work around this. Sometimes the touchesEnded does not get called, BUT, the touches show up as part of all the touches in the event.
Note, that you can now process the same "canceled" or "ended" touch multiple times. If that is a problem, you have to keep your own state of "pending" touches, and remove them when done.
Yeah, it all is pretty bad, but I don't know how to overcome this issue without a similar hack. The basic solution is to look at all the touches in each event, and process them based on their phase, calling the appropriate ended/canceled when they are seen.
- (void) touchesEndedOrCancelled:(NSSet *)touches
{
__block NSMutableSet *ended = nil;
__block NSMutableSet *canceled = nil;
[touches enumerateObjectsUsingBlock:^(UITouch *touch, BOOL *stop) {
if (touch.phase == UITouchPhaseEnded) {
if (!ended) ended = [NSSet setWithObject:touch];
else [ended addObject:touch];
} else if (touch.phase == UITouchPhaseCancelled) {
if (!canceled) canceled = [NSSet setWithObject:touch];
else [canceled addObject:touch];
}
}];
if (ended) [self touchesEnded:ended withEvent:nil];
if (canceled) [self touchesCancelled:canceled withEvent:nil];
}
Then, call it at the end of touchesBegan and touchesMoved...
[self touchesEndedOrCancelled:event.allTouches];
For this to work, touchesEnded/Canceled needs to not choke on a nil event. Also, the "other" needs to be handled. In touchesEnded...
[self touchesCancelled:[event.allTouches objectsPassingTest:^BOOL(UITouch *touch, BOOL *stop) {
return touch.phase == UITouchPhaseCancelled;
}] withEvent:nil];
and in touchesCanceled...
[self touchesEnded:[event.allTouches objectsPassingTest:^BOOL(UITouch *touch, BOOL *stop) {
return touch.phase == UITouchPhaseEnded;
}] withEvent:nil];
You may need to provide more detail but....
If you run your finger off the edge of the screen this event will show touch started and touch moved but no end as it didn't actually end (lift finger). This could be your problem, that the event didn't happen. Plus there is a limit to the amount of touches in multi touch if you press say 10 times its then up to the system to determine what events are really and what are false, it seems like you are using the hardware incorrectly.
I am using - (void) touchesMoved to do stuff when ever I enter a specific frame, in this case the area of a button.
My problem is, I only want it to do stuff when I enter the frame - not when I am moving my finger inside the frame.
Does anyone know how I can call my methods only once while I am inside the frame, and still allow me to call it once again if I re-enter it in the same touchMove.
Thank you.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self.view] anyObject];
CGPoint location = [touch locationInView:touch.view];
if(CGRectContainsPoint(p1.frame, location))
{
//I only want the below to me called
// once while I am inside this frame
[self pP01];
[p1 setHighlighted:YES];
}else {
[p1 setHighlighted:NO];
}
}
You can use some attribute to check if the code was already called when you were entering specific area. It looks like highlighted state of p1 object (not sure what it is) may be appropriate for that:
if(CGRectContainsPoint(p1.frame, location))
{
if (!p1.isHighlighted){ // We entered the area but have not run highlighting code yet
//I only want the below to me called
// once while I am inside this frame
[self pP01];
[p1 setHighlighted:YES];
}
}else { // We left the area - so we'll call highlighting code when we enter next time
[p1 setHighlighted:NO];
}
Simply add a BOOL that you check in touchesMoved and reset in touchesEnded
if( CGRectContainsPoint([p1 frame],[touch locationInView:self.view])) {
NSLog (#"Touch Moved over p1");
if (!p14.isHighlighted) {
[self action: p1];
p1.highlighted = YES;
}
}else {
p1.highlighted = NO;
}
try using a UIButton and use the 'touch drag enter' connection in Interface Builder.