I have used "Register yourself" as text. I want to make it as link. And by clicking on that link, it should open Register.xib.
How can I get this link??
Kareem is right. Create a UIButton object, set it's type to "Custom" and then you can give it a title. Connect the action to a method which pushes or presents your Register view controller and you will be all set.
You'll probably want to give some indication to the user that the text is a clickable link. Either set the color of the text to blue. Another, completely separate option, is to use a NSAttributedString (which has an underline attribute) to indicate to the user that the text is clickable. That requires an Open Source replacement for UITextView (which you can learn more about from this related question).
I think by below you will get what you want...
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSSet *allTouches = [event allTouches];
if ([allTouches count] == 1)
{
UITouch *touch = (UITouch *)[allTouches anyObject];
CGPoint touchPoint = [touch locationInView:self];
// Convert to coordinate system of current view
touchPoint.y -= self.bounds.size.height;
touchPoint.y *= -1;
CGPathRef statusPath = CTFrameGetPath(statusCTFrame);
NSString *touchedInterStr = nil;
if (CGPathContainsPoint(statusPath, NULL, touchPoint, FALSE))
{
touchedInterStr = [self getInteractiveStringInFrame:statusCTFrameatPosition:touchPoint];
}
else
{
return ;
}
}
}
There's a middle ground that's worth mentioning. Using UIButton with UIButtonTypeCustom has some shortcomings, namely that you have limited control over the text styling (e.g. you cannot make it right or left aligned, it will always be centered).
Using NSAttributedString can be overkill, and tough to implement if you're unfamiliar with NSAttributedString, CoreText etc.
A solution I've used is a simple subclass of UIControl which allows more customization than subclassing UIButton and less hassle than NSAttributedString et al. The UIControl approach is only appropriate if the entire string will be a "link".
You would do something like this in your UIControl subclass:
Add a UILabel subview in the init method
Configure the UILabel with your desired backgroundColor, textColor, textAlignment etc, including changes to the UILabel layer if you want the background to appear like Twitter's in-tweet links in their iOS App
Override the setHighlighted: method to customize the look and feel changes on touch/tracking
Add #property setters in the .h file so the owning class can set the state
First, construct the attributed string and save the range of the interactive text and the text itself.
Then, draw the attributed string with CoreText Framework, keep the CTFrameRef.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, bounds);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CTFrameDraw(frame, context);
//CFRelease(frame);
CGPathRelease(path);
The last, override the [view touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event] like this:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSSet *allTouches = [event allTouches];
if ([allTouches count] == 1) {
UITouch *touch = (UITouch *)[allTouches anyObject];
CGPoint touchPoint = [touch locationInView:self];
// Convert to coordinate system of current view
touchPoint.y -= self.bounds.size.height;
touchPoint.y *= -1;
CGPathRef statusPath = CTFrameGetPath(statusCTFrame);
NSString *touchedInterStr = nil;
if (CGPathContainsPoint(statusPath, NULL, touchPoint, FALSE)) {
touchedInterStr = [self getInteractiveStringInFrame:statusCTFrame atPosition:touchPoint];
} else {
return ;
}
}
}
- (NSString *)getInteractiveStringInFrame:(CTFrameRef)frame atPosition:(CGPoint)point
{
CGPathRef path = CTFrameGetPath(frame);
CGRect rect;
CGPathIsRect(path, &rect);
// Convert point into rect of current frame, to accurately perform hit testing on CTLine
CGPoint pointInsideRect = point;
pointInsideRect.x = point.x - rect.origin.x;
CFArrayRef lines = CTFrameGetLines(statusCTFrame);
CFIndex count = CFArrayGetCount(lines);
CGFloat curUpBound = rect.origin.y + rect.size.height;
CFIndex touchedIndex = -1;
for (CFIndex pos = 0; pos < count; pos++) {
CTLineRef curLine = CFArrayGetValueAtIndex(lines, pos);
CGFloat ascent, descent, leading;
CTLineGetTypographicBounds(curLine, &ascent, &descent, &leading);
CGFloat curHeight = ascent + descent + leading;
if (pointInsideRect.y >= curUpBound-curHeight && pointInsideRect.y <= curUpBound){
touchedIndex = CTLineGetStringIndexForPosition(curLine, pointInsideRect); // Hit testing
break;
}
curUpBound -= curHeight;
}
if (touchedIndex == -1)
return nil;
NSEnumerator *enumerator = [interactiveStrs objectEnumerator];
InteractiveString *curString;
while ((curString = (InteractiveString *)[enumerator nextObject])) {
if (NSLocationInRange(touchedIndex, curString.range)) {
return curString.content;
}
}
return nil;
}
You get the interactive text in touchesEnded, then you could do anything you want, like trigger a delegate method.
PS: this is the old-fashion solution, I heard that iOS5 had provided something like an enhanced UIWebView to implement the requirements, maybe you could check the apple sdk document library.
Take a button and set title of the button which you want to set as hyperlink Text. You can choose default and custom button type button in iOS 7 and iOS 8, but for below iOS 7 you have to take button type custom only. You can change colour of the text to highlight text for UI and than add whatever action you want to add on that button click. It will work like charm, good luck.
Take a look to TTTAttributedLabel. Is really simple to configure links, phones, etc.
Related
I have a circular slider, which has a bezier arc drawn in it, an arc has two handles at start and end point in slider, arc is drawn in circular slider.
I am able to draw bezier curve along the circular slider with the help of start and end handles.
I want to change the colour of arc when it is dragged 45 inside or 45 outside from handle only and it should not change arc colour when it is dragged in the circular slider.
Note- while dragging inside and outside it should not change other slider arc's color.
The color should only change, when it is dragged inside and outside only not when it is dragged in circle.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
_dragging = YES;
if (!self.isDragging)
{
return;
}
if ([self.delegate respondsToSelector:#selector(sliderDraggin:)])
{
[self.delegate sliderDraggin:self];
}
UITouch *touch = [touches anyObject];
//UITouch *touch = [touches anyObject];
NSUInteger nearestHandleIndex = [self indexOfNearesPointForTouch:touch];
_nearesHandle = (nearestHandleIndex != NSNotFound) ? _handles[#(nearestHandleIndex)] : nil;
CGPoint location = [touch locationInView:self];
CGFloat distance = [_math distanceBetweenPoint:_centerPoint andPoint:location];
CGFloat inOff = _radius - _sliderWidth - _offset.inside;
CGFloat outOff = _radius + _offset.outside;
if (distance < inOff || distance > outOff)
{
if (self.isNeedToRevoke)
{
_dragging = NO;
return;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
int a = AngleFromNorth(_centerPoint, location, EVA_FLIPPED);
[self moveView:_nearesHandle toAngle:a];
[self drawArc];
});
[self informDelegateAboutHandles];
}
You've calculated the distance.
So, first, don't exit if it's less than < inOff or > outOff.
Instead, set the color based upon distance, perhaps:
UIColor *arcColor;
if (distance < (inOff - 45)) {
arcColor = [UIColor redColor];
} else if (distance > (outOff + 45)) {
arcColor = [UIColor redColor];
} else {
arcColor = [UIColor greenColor];
}
Clearly, tweak the logic as you see fit, but that's likely to be the basic idea.
Use this arcColor to update whatever color-specific property is used by your drawing routine.
I don't have enough to figure out how you rendered these arcs, but this repo contains sample app that updates strokeColor of CAShapeLayer for the individual arcs based upon the touches, yielding the following:
I wouldn't worry too much about reconciling my implementation with yours, but it just illustrates the pattern shown above, where you calculate which arc to update in touchesBegan and update the color in touchesMoved.
I'm using ACEDrawingView to draw within a view.
How would I detect the width and height of the drawing, so that I can crop around it, something like this:
Update: After #Duncan pointed me in the right direction, I was able to look through the source code and found the following:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// save all the touches in the path
UITouch *touch = [touches anyObject];
previousPoint2 = previousPoint1;
previousPoint1 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
if ([self.currentTool isKindOfClass:[ACEDrawingPenTool class]]) {
CGRect bounds = [(ACEDrawingPenTool*)self.currentTool addPathPreviousPreviousPoint:previousPoint2 withPreviousPoint:previousPoint1 withCurrentPoint:currentPoint];
CGRect drawBox = bounds;
drawBox.origin.x -= self.lineWidth * 2.0;
drawBox.origin.y -= self.lineWidth * 2.0;
drawBox.size.width += self.lineWidth * 4.0;
drawBox.size.height += self.lineWidth * 4.0;
self.drawingBounds = bounds; // I added this property to allow me to extract the bounds and use it in my view controller
[self setNeedsDisplayInRect:drawBox];
}
else if ([self.currentTool isKindOfClass:[ACEDrawingTextTool class]]) {
[self resizeTextViewFrame: currentPoint];
}
else {
[self.currentTool moveFromPoint:previousPoint1 toPoint:currentPoint];
[self setNeedsDisplay];
}
}
However I get this when I test the bounds:
I'm going to keep trying to figure it out, but if anyone could help that would be great!
Update 3: Using CGContextGetPathBoundingBox I was finally able to achieve it.
Every time you get a touchesMoved, record the location of the point you are now drawing. When you are all done, you have all the points. Now look at the largest x value and the smallest x value and the largest y value and the smallest y value in all of those points. That's the bounding box of the drawing.
Another approach (which you've already discovered) is to save the CGPath and then call CGContextGetPathBoundingBox. Basically that does exactly the same thing.
Note that a path has no thickness, whereas your stroke does. You will need to inset the bounding box negatively to allow for this (my screencast doesn't do that).
I'm not familiar with the AceDrawingView class. I can tell you how to do it with iOS frameworks though:
Create your path as a UIBezierPath.
Interrogate the bounds property of the path.
I am creating a custom UIControl object as detailed here. It is all working well except for the touch area.
I want to find a way to limit the touch area to only part of the control, in the example above I want it to be restricted to the black circumference only rather than the whole control area.
Any idea?
Cheers
You can override UIView's pointInside:withEvent: to reject unwanted touches.
Here's a method that checks if the touch occurred in a ring around the center of the view:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self] anyObject];
if (touch == nil)
return NO;
CGPoint touchPoint = [touch locationInView:self];
CGRect bounds = self.bounds;
CGPoint center = { CGRectGetMidX(bounds), CGRectGetMidY(bounds) };
CGVector delta = { touchPoint.x - center.x, touchPoint.y - center.y };
CGFloat squareDistance = delta.dx * delta.dx + delta.dy * delta.dy;
CGFloat outerRadius = bounds.size.width * 0.5;
if (squareDistance > outerRadius * outerRadius)
return NO;
CGFloat innerRadius = outerRadius * 0.5;
if (squareDistance < innerRadius * innerRadius)
return NO;
return YES;
}
To detect other hits on more complex shapes you can use a CGPath to describe the shape and test using CGPathContainsPoint. Another way is to use an image of the control and test the pixel's alpha value.
All that depends on how you build your control.
I currently have an application where a user takes a photo or chooses from their library.
On the next controller it will show the image that was selected/taken.
What I would like to do now is show a view of some sort on top of the image view. The view would be translucent round the edges and have a circle which would show the image beneath (not transulcent). Basically this is selected a part of the image.
I then need to save some how where the view is on screen, as it should also be moveable by the user.
What is the best way to approach this?
I need an overlay view which can be moved. The circle would be a fixed size always the inside shows the imageview beneath and the outside would be a 0.5 translucency so you can still see the image but not completely. Then to save the location of the moved around circle?
---- EDIT -----
This is the example image I have created.
I have a view which has a photo as the UIImageView (bottom layer). On top of this I am trying to add a view (like the picture above). Note, the picture above is actually a png as suggested. However, this overlay is moveable. The circle is transparent (0) so you can completely see the photo below it. The outer (grey) is transparent partially (0.5) so you can still see just not completely.
The top view (circle part) would be moved around on the photo to mark a specific point on the photo. In this example if the circle is moved the side (grey) ends on screen, therefore I would need to make a huge image which takes into account the moving of the view -- which is not the best way to do this surely?
EDIT 2 ----
I now have one UIImageView over the top of the photoView (another UIImageView). The overlay is 4 times the screen with a circle in the middle of it.
When the item is moved I have a gesture recognizer that runs:
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
NSLog(#"Pan Gesture");
gesture.view.center = [gesture locationInView:self.view];
}
At present from the above the UIImageView is moved from the middle point, that way the circle is what looks to be moving when the finger moves.
That is all great, but how do I implement the suggested answers into my handlePan method. So I need to check that the middle point is not too close the edge. Ideally I would like a 50 margin around the screen so the circle does not look to go completely (or mostly) off screen?
I understand from the question that you know how to do the movement of the overlay, so what I meant is just that you can simply use image views big enough to make sure that their border isn't visible.
Let's say the UIImageView would be called
UIImageView *photoImageView;
And the overlay with translucent and semi-translucent areas would be
UIImageView *imageOverlayView;
The way I see it, you need 3 different images for that imageOverlayView, depending on the device it's running on - 1 for iPad, 1 for iPhone 5 and 1 for other iPhones.
Width of that image should be equal to (screen width - circle radius) * 2, height should
CGFloat circleRadius; //this would be the radius of translucent circle
So if you set the frame of the overlay properly:
CGRect imageFrame = photoImageView.frame;
imageOverlayView.frame = CGRectMake(circleRadius - imageFrame.size.width/2, circleRadius - imageFrame.size.height/2, (imageFrame.size.width - circleRadius)*2, (imageFrame.size.height - circleRadius)*2); //origin is set to the middle of the image.
you'd never see the edge of the grey area. MZimmerman6's answer for implementation of the movement is good, but you should also make sure you block the circle from getting out of the borders of underlying image.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint new = [touch locationInView:imageOverlayView];
delX = new.x - prevPoint.x;
delY = new.y - prevPoint.y;
CGRect overFrame = imageOverlayView.frame;
overFrame.origin.x += delX;
if (overFrame.origin.x < -imageFrame.size.width) {
overFrame.origin.x = -imageFrame.size.width;
}
else if (overFrame.origin.x > 0) {
overFrame.origin.x = 0;
}
overFrame.origin.y += delY;
if (overFrame.origin.y < -imageFrame.size.height) {
overFrame.origin.y = -imageFrame.size.height;
}
else if (overFrame.origin.y > 0) {
overFrame.origin.y = 0;
}
[overlayView setFrame:overFrame];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint new = [touch locationInView:imageOverlayView];
delX = new.x - prevPoint.x;
delY = new.y - prevPoint.y;
CGRect overFrame = imageOverlayView.frame;
overFrame.origin.x += delX;
if (overFrame.origin.x < -imageFrame.size.width) {
overFrame.origin.x = -imageFrame.size.width;
}
else if (overFrame.origin.x > 0) {
overFrame.origin.x = 0;
}
overFrame.origin.y += delY;
if (overFrame.origin.y < -imageFrame.size.height) {
overFrame.origin.y = -imageFrame.size.height;
}
else if (overFrame.origin.y > 0) {
overFrame.origin.y = 0;
}
[overlayView setFrame:overFrame];
}
EDIT
With your pan gesture recognizer, checking if you aren't going too far needs a little change to the handlePan: method.
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
CGPoint newCenter = [gesture locationInView:self.view];
if (newCenter.x < 50) {
newCenter.x = 50;
}
else if (newCenter.x > self.view.frame.size.width - 50) {
newCenter.x = self.view.frame.size.width - 50;
}
if (newCenter.y < 50) {
newCenter.y = 50;
}
else if (newCenter.y > self.view.frame.size.height - 50) {
newCenter.y = self.view.frame.size.height - 50;
}
gesture.view.center = newCenter;
}
If that 50 points margin is equal to circle radius, this will make sure your circle able to touch the edges of the screen, but unable to go beyond them.
I would first initialize some UIImageView with a png image in it that will easily handle this moving frame with a hole in the center. You can the add this to your screen. Once this is done, use the touchesBegan, touchesMoved and other touch commands to find whether a user is touching that box and from that determine how far the user has moved from the previous point. Use this difference to then add or subtract values from the images current frame origin or center, and voila you have a moving box.
EDIT
In View.h
CGPoint prevPoint;
float delX;
float delY;
UIView *overlayView;
In View.m
-(void) viewDidLoad {
overlayView = [UIView alloc] initWithFrame:CGRectMake(100,100,100,100)];
[overlayView setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:overlayView];
UIImageView *circImage = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,overlayView.frame.width,overlayView.frame.height)];
[circImage setImage:[UIImage imageNamed:#"SomeImage.png"]];
[overlayView addSubview:circImage];
[self.view setNeedsDisplay];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
prevPoint = [touch locationInView:overlayView];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint new = [touch locationInView:overlayView];
delX = new.x - prevPoint.x;
delY = new.y - prevPoint.y;
CGRect overFrame = overlayView.frame;
overFrame.x += delX;
overFrame.y += delY;
[overlayView setFrame:overFrame];
[self.view setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint new = [touch locationInView:overlayView];
delX = new.x - prevPoint.x;
delY = new.y - prevPoint.y;
CGRect overFrame = overlayView.frame;
overFrame.x += delX;
overFrame.y += delY;
[overlayView setFrame:overFrame];
[self.view setNeedsDisplay];
}
I have an NSMutableAttributedString such as "Bob liked your picture".
I'm wondering if I can add two different tap events to "Bob" and "picture". Ideally, tapping "Bob" would present a new view controller with Bob's profile and tapping "picture" would present a new view controller with the picture. Can I do this with NSMutableAttributedString?
You can achieve this by using CoreText to implement a method that will retrieve the index of the character the user selected / touched. First, using CoreText, draw your attributed string in a custom UIView sub class. An example overridden drawRect: method:
- (void) drawRect:(CGRect)rect
{
// Flip the coordinate system as CoreText's origin starts in the lower left corner
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0.0f, self.bounds.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(_attributedString));
if(textFrame != nil) {
CFRelease(textFrame);
}
// Keep the text frame around.
textFrame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path.CGPath, NULL);
CFRetain(textFrame);
CTFrameDraw(textFrame, context);
}
Secondly, create a method that interrogates the text to find the character index for a given point:
- (int) indexAtPoint:(CGPoint)point
{
// Flip the point because the coordinate system is flipped.
point = CGPointMake(point.x, CGRectGetMaxY(self.bounds) - point.y);
NSArray *lines = (__bridge NSArray *) (CTFrameGetLines(textFrame));
CGPoint origins[lines.count];
CTFrameGetLineOrigins(textFrame, CFRangeMake(0, lines.count), origins);
for(int i = 0; i < lines.count; i++) {
if(point.y > origins[i].y) {
CTLineRef line = (__bridge CTLineRef)([lines objectAtIndex:i]);
return CTLineGetStringIndexForPosition(line, point);
}
}
return 0;
}
Lastly, you can override the touchesBegan:withEvent: method to get the location of where the user touched and convert that into a character index or range:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *t = [touches anyObject];
CGPoint tp = [t locationInView:self];
int index = [self indexAtPoint:tp];
NSLog(#"Character touched : %d", index);
}
Be sure to include CoreText into your project and clean up any resources (like text frames) you keep around as that memory is not managed by ARC.
The way I would handle it is using a standard NSString in a UITextView. Then taking advantage of the UITextInput protocol method firstRectForRange:. Then you could easily overlay an invisible UIButton in that rect and handle the action you'd like to take.