What I want:
In an iPhone app, I'd like to show information in a tableView.
In each cell, the text is like: John recently listen to music abcdefg.mp3.
and if needed, the text can have two lines.
In the text, a.mp3 should be clickable so when the user touches the abcdefg.mp3 part, another page will be invoked. When user touches abcdefg.mp3, it will also have some effects, just like touching a button.
What I do:
I calculate the frame of the text, and I use a UIButton for abcdefg.mp3.
My Problem:
Sometimes abcdefg.mp3 may be in multiline, like:
abc is at the end of the first line
defg.mp3 is in second line.
What should I do in this case?
I've already searched about: Create tap-able "links" in the NSAttributedString of a UILabel?
However I think it is not suitable here as the clickable text is all in one line in the sample.
The most simple way is to just add a gesture recognizer to the actual view (be it a UILabel or some custom view of your own). In order for the gesture recognizer to work, the view must be set userInteractionEnabled.
Here's an example, assuming that your label view (or whatever it is) is called labelView:
UITapGestureRecognizer* gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(userTappedOnLink:)];
// if labelView is not set userInteractionEnabled, you must do so
[labelView setUserInteractionEnabled:YES];
[labelView addGestureRecognizer:gesture];
In this example, an action message will be sent to self and the message would be defined as
- (void)userTappedOnLink:(UIGestureRecognizer*)gestureRecognizer;
This works the same as wiring up any other UIControl subclass, such as a button.
Other notes: don't try to add the same gesture recognizer to multiple views, it won't work. Don't add more than one copy of the gesture recognizer to multiple views (it doesn't replace them, it just stacks them up and wastes memory). You should add the gesture recognizer when you initially create and configure your view.
For more information, see the documentation for UIGestureRecognizer.
Swift 4.2 Version:
var labelView = UILabel()
let gesture = UITapGestureRecognizer(target: self, action: #selector(userTappedOnLink))
// if labelView is not set userInteractionEnabled, you must do so
labelView.isUserInteractionEnabled = true
labelView.addGestureRecognizer(gesture)
#objc func userTappedOnLink() {
print("clicked!")
}
Swift 2.0 version:
func userTappedOnLink() {
print("clicked!")
}
///tap and link to FB page
let gesture = UITapGestureRecognizer(target: self, action: "userTappedOnLink")
// if labelView is not set userInteractionEnabled, you must do so
lblStaff.userInteractionEnabled = true
lblStaff.addGestureRecognizer(gesture)
Sounds similar to what they accomplished in Twitteriffic with Fancy UILabels. Basically, Craig Hockenberry made his own custom "data detector", so that he could insert links within labels and multi-line text. See this question: Is there a way to create custom UIDataDetectorTypes?
You could also just put an "invisible Button" above, by using a custom button without text and images.
I think you can use textView and diable the scrolling. I did same for my project where I need to point to website addresses. just uncheck everything as shown in screenshot in 'scroll view' section in inspector window.
By adding UITapGestureRecognizer to the label, to make UIlabel clickable.
Before adding the label to tapgesture, don't forgot to enable userinteraction for UIlabel
Here is the sample code:
//Tap Gesture
UITapGestureRecognizer* gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(userTapped:)];
//Adding userinteraction for label
[labelName setUserInteractionEnabled:YES];
//Adding label to tap gesture
[labelName addGestureRecognizer:gesture];
This solution for clickable UILabel. It isn't for select link in text. Just nice solution in my opinion for clickable UILabel like UIButton:
#import <UIKit/UIKit.h>
#protocol ClickableLablelDelegate <NSObject>
#required
- (void)onClickLabel:(UILabel *) label;
#end
#interface ClickableLable : UILabel
#property (nonatomic, weak) id <ClickableLablelDelegate> delegate;
#end
#import "ClickableLable.h"
#implementation ClickableLable
-(void)awakeFromNib
{
[super awakeFromNib];
[self setUserInteractionEnabled:YES];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self setBackgroundColor:[UIColor lightGrayColor]];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self setBackgroundColor:[UIColor clearColor]];
if ([_delegate respondsToSelector:#selector(onClickLabel:)]) {
[_delegate onClickLabel:self];
}
}
-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self setBackgroundColor:[UIColor clearColor]];
}
Related
In my view I want to add a target which should be fired when I click the view. I can do the same through IB or I have done it for buttons even in code. However I have no idea how to do it for UIView programatically.
Anyone has done that before.
Help me.
For clicking a UIView you have to use UIGestureRecognizer or UITouch. This would only help in prompting an action. The UIButton has a selector method whereas the UIView does not have any such method. Also , this is same for UIImageViews etc also.
You can acheive this using UIGestureRecognizer.
Step 1:
Add your UIView as a property in your viewcontroller
#property (strong, nonatomic) IBOutlet UIView *yourView;
Step 2:
Set UIGestureRecognizer for your UIView.
- (void)viewDidLoad {
[super viewDidLoad];
UIGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self.yourView addGestureRecognizer:gesture];
}
Step 3:
Handle the click on UIView.
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {
//to get the clicked location inside the view do this.
CGPoint point = [gestureRecognizer locationInView:self.yourView];
}
Remember that A UIGestureRecognizer is to be be used with a single view.
i am making a chat application, and in the chat window there are uilabels in uitableviewcells. initially the keyboard will be present but when the user touches on any place on uitableview, i will make the chat window as fullscreen (dissappearing keyboard).
i cant find a way/trick to accomplish this.
i have tried the following method: by using tableview:didselectrowatindexpath, i am able to do it but, user needs to press on an existent uitableviewcell. but i want to understand the press even when uitableview is empty..
note: my chat tableview is interactive e.x. some rows will include image button which need to be pressable, so i cant just put an invisible button onto uitableview.
thank you for your thoughts
Aytunc Isseven
What you want to do is add a gesture recognizer to the UITableView that responds to the appropriate gestures. I would recommend against using UITapGestureRecognizer as the UITableView is already using taps for selecting the cells, so you might want to try the UILongPressGestureRecognizer. I put together a small sample of how you can do this as follows:
In my viewDidLoad I did the following:
UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPressFrom:)];
[self.tableView addGestureRecognizer:gesture];
and the handleLongPressFrom: is as follows:
- (void)handleLongPressFrom:(UILongPressGestureRecognizer *)recognizer {
NSLog(#"handleLongPressFrom: %#", recognizer);
// Add real code here
}
The full list of gestures can be found here.
Oh, if you did want to still use tap, check out this stack overflow question. I don't know if the method presented works fully, but it'd be a good place to start.
Using UITapGestureRecognizer with a UITableView:
Okay, since the tap gesture seems to be the correct one for your use case you can try and do the following. Step 1 is to set up the gesture recognizer as I listed above using the tap gesture instead of the long press gesture.
The code in viewDidLoad is very similar with an important addition ...
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapFrom:)];
// we need to set the gesture delegate so we can allow the tap to pass through to the
// UITableViewCell if necessary.
gesture.delegate = self;
[self.tableView addGestureRecognizer:gesture];
The handleTapFrom: function is pretty much the same with just the different gesture recognizer as the parameter.
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
NSLog(#"handleTapFrom: %#", recognizer);
// Add real code here
}
The major changes to this approach is that we need to implement the UIGestureRecognizerDelegate protocol. Since our goal is to allow the tap gesture to pass through the UITableView to it's subviews (i.e. the UITableViewCell and it's components) we need to implement the gestureRecognizer:shouldRecieveTouch: function. The following implementation should cover what you are attempting.
#pragma mark UIGestureRecognizerDelegate methods
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// If the view that is touched is not the view associated with this view's table view, but
// is one of the sub-views, we should not recognize the touch.
if (touch.view != self.tableView && [touch.view isDescendantOfView:self.tableView]) {
return NO;
}
return YES;
}
The isDescendantOfView: function returns YES if the view it is testing against is the same as the view doing the testing, so we need to accommodate that case separately. You can generify this function by using gestureRecognizer.view instead of self.tableView, but I didn't think it was necessary in this case.
The trick is to make your viewController put a tap recognizer on the view but make it always opt out by returning NO from the delegate method "gestureRecognizerShouldBegin". That way gestureRecognizerShouldBegin gets called for every touch on the view, but you don't interfere with the normal event handling of the table.
- (void)viewDidLoad {
[super viewDidLoad];
[self detectTouchesOnView:self.tableView];
}
- (void)detectTouchesOnView:(UIView*)theView {
UITapGestureRecognizer* tapR = [[UITapGestureRecognizer alloc]initWithTarget:nil action:nil];
tapR.delegate = self;
[theView addGestureRecognizer:tapR];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// React to the UITableView being touched E.G. by hiding the keyboard as below.
[self.view endEditing:YES];
return NO;
}
I'm trying to hide the number pad, but I do not want to implement a button.
Is there a way to dismiss the number pad when the user taps outside the textfield?
This is one of those questions where you read it and say "That's easy you just..". And then you go to do it and make it super complicated. And then realize it doesn't have to be that complicated.
The answer I've come up with, and I'm sure it will help someone else, Is to use an invisible UIView that never interacts but acts on other views and maybe not in the way you'd think.
The typical answer to a question about dismissing the UIKeyboardTypeNumberPad keyboard is to add a bar that has a button as the inputAccessoryView to dismiss the keyboard. If a bar and button are undesirable generally you just listen for touch events on the background and your good to go but this question is about a tableview and that makes this much harder.
But this inputAccessoryView feature is still awesome. It allows you to define a UIView or UIView subclass to be displayed when the keyboard is shown. More importantly when the keyboard is shown due to a textfield for which it is the inputAccessoryView becoming first responder.
I could yammer on but first here is some code for a lightweight class that actually performs very well in testing.
The contents of NJ_KeyboardDismisser.h are:
#import <UIKit/UIKit.h>
// For some reason neither inputView or inputAccessoryView are IBOutlets, so we cheat.
#interface UITextField (WhyDoIHaveToDoThisApple)
#property (readwrite, retain) IBOutlet UIView *inputAccessoryView;
#end
#interface NJ_KeyboardDismisser : UIView
#property (nonatomic, weak) IBOutlet UIView *mainView;
-(id)initWithMainView:(UIView *)view; // convienience method for code
#end
And the contents of NJ_KeyboardDismisser.m are:
#import "NJ_KeyboardDismisser.h"
#implementation NJ_KeyboardDismisser {
UITapGestureRecognizer *_tapGR;
}
#synthesize mainView = _mainView;
-(void)setMainView:(UIView *)view{
if (_tapGR) [_tapGR.view removeGestureRecognizer:_tapGR];
_mainView = view;
_tapGR = [[UITapGestureRecognizer alloc] initWithTarget:_mainView action:#selector(endEditing:)];
}
-(id)initWithMainView:(UIView *)view{
if ((self = [super initWithFrame:CGRectMake(0, 0, 0, 0)])){
self.mainView = view;
}
return self;
}
-(void)didMoveToWindow{ // When the accessory view presents this delegate method will be called
[super didMoveToWindow];
if (self.window){ // If there is a window one of the textfields, for which this view is inputAccessoryView, is first responder.
[self.mainView addGestureRecognizer:_tapGR];
}
else { // If there is no window the textfield is no longer first responder
[self.mainView removeGestureRecognizer:_tapGR];
}
}
#end
You may recognize the endEditing: method, as mentioned by Cosique, it is a UIView extension method that asks a views nested textfield to resign. Sound handy? It is. By calling it on the tableview the textfield it contains resigns first responder. Since this technique works on all UIViews there is no need to artificially limit this outlet to only UITableViews so the outlet is just UIView *mainView.
The final moving part here is the UITapGestureRecognizer. We don't want to add this recognizer full time for fear of screwing up the tableview's workings. So we take advantage of UIView's delegate method didMoveToWindow. We don't really do anything with the window we just check to see if we are in one; If we are then one of our textfields is first responder, if not then it's not. We add and remove our gesture recognizer accordingly.
Okay straightforward enough, but how do you use it? Well if instantiating in code you could do it like this, in tableView:cellForRowAtIndexPath::
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
UITextField *field = [[UITextField alloc] initWithFrame:CGRectMake(20, 6, 100, 31)];
[cell.contentView addSubview:field];
field.keyboardType = UIKeyboardTypeNumberPad;
field.inputAccessoryView = [[NJ_KeyboardDismisser alloc] initWithMainView:self.view];
}
If you are using static cells in a storyboard then the technique is different (obviously). First drag out a generic NSObject and place it in the dark grey strip below the view (where the other objects such as the view controller are). Then change this new object's class to be NJ_KeyboardDismisser. Then connect the "Keyboard Dismisser"'s mainView property to that view (generally a tableview). Then connect the inputAccessoryView property from any each text field in that scene you wish to the "Keyboard Dismisser".
Give it a try! The tableview acts normally. Apple's tap recognizer is smart enough to ignore the swipes on the table, so you can scroll. It also ignores touches in the textfields so you can edit and select other textfields. But tap outside a textfield and the keyboard is gone.
Note: This class's use is not limited to tableviews. If you want to use it on a regular view, just set the mainView property to be the same as the view controller's view.
The easiest way is to do this in your view controller:
[self.view endEditing: YES];
You can resign the responder inside the below function for your view:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
Make sure your view is enabled for user interaction.
when creating the text field add a tag to it.
like this Yourtextfield.tag = 1;
and in you touchesEnded method
do this :
UITextField *resignTextField = (UITextField *)[self.view viewWithTag:1];
[resignTextField resignFirstResponder];
could anyone tell me how to place a translucent black mask over the whole screen, but with the area of a particular UIView being excluded? I want to use this mask over a UITextField, which calls resignFirstResponder when the outside part of the textfield is tapped.
The subview tree would be like:
UIWindow
|-UIView
| |-UITextField
|
|-Mask
Thanks,
You can use the:
- (void)bringSubviewToFront:(UIView *)view
And send the UITextField to the front after you add the black mask view.
UPDATE
Ok this are the steps to do it (you can see the apple example for UIGestureRecognizers for more)
create a mask view (programmatically or with IB) and call it "maskView".
create a gestureRecognizer and add it to the maskView.
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapFrom:)];
recognizer.delegate = self;
UIImageView *maskView = [[UIImageView alloc] init];
[maskView addGestureRecognizer:recognizer];
you will need to set your view controller as the delegate for "UIGestureRecognizerDelegate"
#interface YourViewController : UIViewController <UIGestureRecognizerDelegate>
add the maskView to your ViewController when you want to mask the screen. and then move the text field above the mask.
[self.view addSubView:maskView];
[self.view bringSubviewToFront:textField];
set this 2 functions:
in the first one you can set the action if the user touches the mask
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
//resign the first responder when the user taps the mask
//you can remove the mask here if you want to
}
in the second one you tell the app not to receive touches from the textField
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Disallow recognition of tap gestures in the segmented control.
if ((touch.view == textField)) {//checks if the touch is on the textField
return NO;
}
return YES;
}
Hope it make some sense
shani
I've a UITextView (from the InterfaceBuilder) in my View. This contains a UIScrollView.
Now I have to detect touch Events inside the UITextView beacause I must close my own submenus.
The Apple UITextView is in a UIScrollView, and to detect a touch in a UIScrollView I've to ovveride the UITouch Funktion of UITextView's UIScrollView.
Any suggestions how I can do this?
UITextView is a subclass of UIScrollView. Is this what you mean when you say "contains a UIScrollView?"
There are a couple of approaches you could take here. If the touch you are concerned with is the first touch in the text view, and is therefore beginning editing, you can become its delegate and implement this method:
- (void)textViewDidBeginEditing:(UITextView *)textView
If you need to be aware of any tap that occurs inside the text view, not just initial editing taps, you can use a UITapGestureRecognizer to listen for taps. Something like this:
// in the method where you configure your view
UITapGestureRecognizer *tap = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textViewTapped:)] autorelease];
// assuming textView is pointing to your UITextView
[textView addGestureRecognizer:tap];
And then implement the action method elsewhere in your class:
- (void)textViewTapped:(id)sender {
// dismiss your menu or whatever
}
Note that I haven't actually tested this scenario, but it should work. Gesture recognizers are awesome.