Add UIGestureRecognizer to individual letters of UILabel - ios

Right Now I am making an iOS app and I would like to implement the ability to remove letters in a UILabel by simply "Dropping the letter". The part That I am having an issue with is adding a gesture recognizer to individual letters of a UILabel. I have already searched the web for this and have nothing.Just to be clear, I am NOT adding a gesture recognizer to the whole label, I am only wanting to add it to individual letters. Any help is greatly appreciated.
Thanks

It seems that the easiest way to do it is by dynamically calculating the position of a letter. Use this:
CGSize textSize = [text sizeWithFont:[UIFont boldSystemFontOfSize:size]
constrainedToSize:constrainedSize
lineBreakMode:NSLineBreakByWordWrapping];
so you can get the the size for a letter in the font and size you are using for each label and using the [stringInstance length] property and the [UILabel numberOfLines] property to get the approximate center for each letter in a label, then use a simple tapGestureRecognizer for each label and in there calling your method for
- (NSString*)letterForPoint:(CGPoint)tapCenter inLabel:(UILabel*)label;
there you use everything to calculate the approximate center for each letter and adding a selectableRange for error and correct user responding as x +- 20 pixels and y +- 20 pixels.
Apple says that anything with a selectable bound lesser than 40 pixels for 40 pixels will be completely annoying for the user, so your font size should actually be quite big for user interaction.

If I am understanding correctly, it sounds like subclassing UILabel would make sense.
Make a LetterLabel: UILabel class
and in the init set up your GestureRecognizer on self.
Then when you create your letters each one will have the recognizer attached to it
LetterLabel *firstLetter = [[LetterLabel alloc] init]
LetterLabel *secondLetter = [[LetterLabel alloc] init]

UIGestureRecognizer can only be applied to a UIView or subclass of that (e.g. a UILabel, like Adam suggested). If you are worried about performance, then I think your next step would be to:
1) Subclass UIView in order to create a custom implementation of a UILabel-like view.
2) Draw out the custom label's string to in the drawInRect: method
3) Use the touchesBegan:withEvent:, touchesMoved:withEvent:, and touchesEnded:withEvent: methods to track finger positions in order to move/redraw characters of the backing string.
EDIT:
Alternatively, you could use one UIPanGestureRecognizer on a the custom label to track finger positions, and move around sublayers within the bounds of the custom label (each sublayer could contain a character in the string). In this way, you could more easily take advantage of Core Animation to animate the characters (i.e. create the "dropping" effect).

Related

Adding UITextField inline UILabel (Fill in the blanks)

I am trying to achieve something like this for iOS in Objective C.
The fill in the blanks(UITextField) should be inline, and should be able to have its own inputType.
Also, each View is a type of cell.contentView of a UITableViewCell.
My current approach is to find the length of string and also calculate the wrapping content length to the next line. Calculate the x's and y's for UITextField and add another UILabel after the UTextField
Is there any other approach other than this?
As EmilioPelaez says, this is not exactly an answer to your question, but a suggestion:
You can use a collection view with an horizontal flow for each "sequence" (i.e. UILabel-UItextfield-etc...)
That collection view has 2 kind of cell:
One with a uilabel with the number of line set to "1"
and the correct layout to fit the cell.
Another with a uitextfield and the correct layout
Coupled with:
My current approach is to find the length of string and also calculate the wrapping content length to the next line.
You may be able to easily adjust the width of the different cells, hide a uitextfield (if needed) and display a more dynamic "sequence" (if needed)
This is not exactly an answer to your question, instead it's a suggestion for a different interaction.
I think that instead of using inline textFields, you could use a UILabel with an attributed string, and in where the textFields would be, you add a different character with a different color that you can tap (For example, this character ✚).
When tapped, you can show an overlay with a text input, and once that input is completed, you update the label with the text (still tappable, and with a different color).
I think this answer might also be relevant: Detecting taps on attributed text in a UITextView in iOS
I think your solution of separating the UILabels and calculating their required positions is a good one for versions lower than iOS9, but if you can count on iOS, UIStackView can dramatically simplify the process for you. You can read more about the process in this tutorial:
UIStackView Tutorial
Good luck!

How to make a vertical UITextView by subclassing it from UIScrollView in Swift?

Background
I'm new to iOS development, but in order start making even the most rudimentary apps using the vertical Mongolian script, I need to have a vertical UITextView. I haven't found any other Mongolian app developers sharing their code so I am trying to make it myself. Mongolian is written from top to bottom and lines wrap from left to right as is illustrated in the following graphic:
Scrolling should be horizontal (left to right).
In a previous question I proposed a combination of a mirrored font and a rotation and mirroring of the UITextView (because that is what I had done in Android). However, after reading this answer and doing further research into the iOS way of doing things (like using TextKit), I'm thinking it may be better to just create something like UIVerticalTextView from scratch rather than messing with the normal UITextView. And since I see that UITextView inheirits from UIScrollView, I assume I should subclass UIScrollView to make the UIVerticalTextView.
Where I am at now
The answer I mentioned above subclassed UIView but I was having a lot of trouble getting scrolling to work. This is another reason I want to subclass UIScrollView. Also that solution did not relayout the words in each line when there was an orientation change.
I've gathered the following pieces but I'm not sure how to put them together:
NSTextStorage - There shouldn't be anything different with this. It will just store the Mongolian Unicode text and any styling information.
NSTextContainer - The height and the width need to be switched to give the vertical text container size. (I think so, anyway. Unless this can be done somewhere else.)
NSLayoutManager - Does anything special need to be done here or is that taken care of by the UIVerticalTextView?
UIVerticalTextView - This will be a subclass of UIScrollView but what are the methods that need to be added and/or overridden?
class UIVerticalTextView: UIScrollView {
// Which properties to include?
// - TextStorage
// - TextContainer
// - LayoutManager
// which methods to override?
// which methods to add to give minimal functionality like UITextView?
// - init
// = text
}
Updates
These questions are attempts to break the problem down into smaller pieces:
How to make UITextView from scratch?
How to Initialize NSTextStorage with a String in Swift
And trying with a different approach:
Are rotated Views compatible with Auto Layout?
Rotating a view in layoutSubviews This actually has a working solution now. However, it basically involves stacking three views on top of each other. I would still prefer to make a custom UIScrollView subclass that uses TextKit as I have presented in my current question.
If you have a font that displays your text the easiest way would be to use the normal UITextView:
UITextView *textView = [[UITextView] alloc] init];
textView.transform = CGAffineTransformMakeRotation(-M_PI/2);
textView.text = #"YOUR TEXT HERE";
[textView setBaseWritingDirection:UITextWritingDirectionRightToLeft forRange:[textView textRangeFromPosition:[textView beginningOfDocument] toPosition:[textView endOfDocument]]];
The textView will now scroll to the right not down, I assume that is supposed to happen for mongolian?

Making some parts of a `UITextField` un-selectable

I have a UITextField that will represent an integer number with a fraction (numerator and denominator). As an example: "27 3/16". I want to make the denominator "/16" at the end both un-editable and also un-selectable.
I can use the delegate method textField:shouldChangeCharactersInRange:replacementString: to prevent the "/16?" from being edited with an approach a bit like this.
Is there some way that I can also prevent the "/16" from being selectable at all? So, the caret can't be moved in to it, and the selection marque can't be made around it.
If this isn't possible, is there a hook so that once the user finishes placing their selection, I can update the selection and move the caret to just before this piece of the text.
Thanks.
You can have a UILabel and UITextField constrained via autolayout next to each other, the UILabel containing the denumerator and the UITextField the numerator.
Here is an answered question for how to do that: Using Auto Layout to have UILabel and UITextField next to each other (the essence is, you need to adjust the content hugging priority of the UITextField to make it always as wide as the contained text.
If your text field gets too small to be tapped, you can apply the code of this answer to a UITextView and make the tappable area of your view bigger: UIButton: Making the hit area larger than the default hit area (but better use method swizzling than overriding a method in a category like in the answer! Or a subclass.)

How to extract sub-string from UILabel based on finger pressed location?

I have a UILabel which contains text.
Once I press on this UILabel, I want to extract one word from the location where my finger pressed.
I've searched a bit on internet, but have no clue about how to implement it.
Does anyone here know how to do it?
Thanks in advance.
Once I press on this UILabel, I want to extract one word from the
location where my finger pressed.
That's not something that's going to be easy to do with UILabel. UILabel is meant to be a simple way to put static text on the screen; it doesn't provide features that would let you determine the frames of individual words.
You'll probably be better off creating your own view for this. You'll want to dig into Core Text to lay out and draw the text. Core Text is a lot more complicated than just using a simple UILabel, but it gives you the information and control you'll need to determine where each word is drawn on the screen. Your view can use that information to map touches to words.
The easiest way is to just make each individual word a separate label, so that all you have to do is add a UITapGestureRecognizer to the label and grab the text from it when it's touched.
If you want to do it with one label, though, here's some pseudocode of a way that could work:
Add a UITapGestureRecognizer to the label. When touched, get the location of the touch.
Use NSString's - (CGSize)sizeWithFont:(UIFont *)font method to get the size of the label's entire string (let's call this stringSize).
Compare stringSize.width to the width of your label to figure out where the text begins (if your label is center or right aligned, this might not be at the label's starting X coordinates). ie. startingX = label.width - stringSize.width / 2.
Now use NSString's - (NSArray *)componentsSeparatedByString:(NSString *)separator method to separate the string in your label into each word, with the space character as the delimiter. Let's store this into an array called wordsAr.
Create a variable, currentXPos, and initialize it to startingX.
Now create a for loop that goes through each word in wordsAr, calculates the size of that word using - (CGSize)sizeWithFont:(UIFont *)font, and adds it to currentXPos. When currentXPos >= the x location of your touch value, you've found the word that you tapped on.
You can use TTTAttributedLabel in the following link: https://github.com/mattt/TTTAttributedLabel
Define your clickable texts as Links for the Label, then you'll get exactly what you need.

Add multiple lines to detailTextLabel in UITableViewCell

I'm using a UITableView with cell style of UITableViewCellStyleValue1.
I'm want to have multiple lines in the detailTextLabel, is that possible? Or do I have to make a custom cell?
You can set cell.detailTextLabel.numberOfLines = 2 to get 2 lines in there. However, I doubt UITableViewCell will lay out the labels as you expect in that case. You may want to subclass UITableViewCell and override -layoutSubviews to position the labels how you want. You can call [super layoutSubviews] and then just tweak the positions of the labels. You'll probably want to use -[NSString sizeWithFont:constrainedToSize:lineBreakMode:] to calculate the correct size for the detail text label.
Alternatively, instead of subclassing UITableViewCell, you could try doing the tweaks in -tableView:willDisplayCell:forRowAtIndexPath:, though if the cell ever decides it needs to re-layout, then your tweaks will be erased. I recommend you go with the subclassing approach.
Edit: BTW, with the subclass approach, all you have to do is change [UITableViewCell alloc] to [MyTableViewCellSubclass alloc]. Since you're not introducing new methods or properties, the variable can still remain typed as a UITableViewCell and you won't have to change any other code.
The detailTextLabel is a UILabel, the word wrapping rules apply just like with a label you create.
http://developer.apple.com/iphone/library/documentation/uikit/reference/UILabel_Class/Reference/UILabel.html#//apple_ref/occ/instp/UILabel/lineBreakMode
EDIT: Just thought, you'll also need to change the height of the cell if the text gets beyond some threshold of lines. probably 2 or 3 is okay.
At least on iOS 11 it works great just adding this line.
cell.detailTextLabel.numberOfLines = 2;
On SWIFT 4.0 and above:
cell.detailTextLabel?.numberOfLines = 0
Unless you never want to have more than 2 lines, do not set numberOfLines to 2 as advices in the comments because it's not an efficient way to code. It's not dynamic. The number of lines might be different base on your screen size, accessibility settings etc. Setting numberOfLines to 0, let the OS use as many lines necessary to display properly your text.

Resources