I have an app with a handful of UITextViews of various sizes. It appears that if a UITextView small enough has a font.pointSize high enough, there's no way to add a space after the text is big enough to fill the text view.
For example:
I'm trying to figure out what was going on here, I starting typing my usual debug string, I typed "What" hit the space key and no space appeared (but this is usual). I typed "the" and the looked like it was tacked right onto the end of the previous word. Sure enough, there was no space. I could go back and add the space just fine and once I do add the space, I can add other spaces as word wrapping then becomes effective.
Another mysterious behavior is that when you double tap space, it doesn't add a period to the end. It replaces the last character with a period. So, "What" + space + space becomes "Wha."
Now, I'm doing some interesting things with the font size like I'm automatically resizing the font so that the text fills the space provided within reasonable bounds, but when I disable that, I can still reproduce the behavior. The only difference then is that instead of fitting on one line, the word wraps to the next line.
For example, if I type "What" + space + "the" it comes out "Whatthe" with "What" on the first line and "the" on the second (though I can only see the tops of the "the." Further, here's some log information from the textViewDidChange:.
Character textView.text.length
--------- --------------------
W 1
h 2
a 3
t 4
<space> 4
t 5
h 6
<space> 7 <---- Here's a wierd one . . . now spaces all
? 8 work fine unless it's resizing
Another mysterious behavior is that when you double tap space, it
doesn't add a period to the end. It replaces the last character with a
period. So, "What" + space + space becomes "Wha."
That is not mysterious. That means that your UITextView uses autocorrection.
Fix:
UITextView *txtView; // your UITextView
[txtView setAutocorrectionType:UITextAutocorrectionTypeNo];
About spaces... as space is not a drawable symbol, your UITextView can't measure is size (width differs with different alignment), so your UITextView's contentSize property won't be changed and behaves mysteriously. You should set contentSize manually (you can easily calculate size of your NSString with sizeWithFont: method).
Related
I noticed that UILabel doesn't wrap words fairly. It glues the last short word (10 or less characters) to the previous one and moves them together on the second line.
Check the illustration:
Label #1: There is enough room in the first line for word 'seven' (as expected).
Label #2: One more 'short' word added and word 'seven' migrated to the second line (I'd like to avoid this behaviour).
Label #3: The last word contains 10 characters and still is treated as 'short' by the Label.
Label #4: The last word contains 11 characters and is now detached from word 'seven' that goes back to the first line (as expected).
Seemingly, Apple fights with 'widows' and forces to a 'good typography' by this behaviour. But sometimes I don't need such care. So, how can I prevent UILabel from glueing last words to the previous one?
Fixed by using NSAllowsDefaultLineBreakStrategy set to false.
Most fonts on iOS/macOS are proportional and the typeface system attempt to use good typography standards. Therefore the characters and/or typography will change the spacing based on other characters around it, line length etc. To get more predictable text display, use a font that has fixed spacing like Monaco or other fixed spacing fonts. This will produce a much more predictable display.
Here is Apple's Typography guidelines to start at:
https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/typography/
I love the San Francisco font. And would like to use it if possible. I have a table view that shows a series of labels where each label is 6 hexadecimal digits, e.g.
F0:1A:2B
12:CE:88
The problem is that they don't line up nicely. I can enable the monospaced digits attribute for the font, but that doesn't account for the characters ABCDEF. I've tried the single monospace font (Menlo) and it looks terrible and out of place.
I'm toying with making 8 little labels one for each character (6 hex digits plus the two : separators), which seems like a huge kludge. Is there no other way. I wondered if there was a way to do something with AttributedString to get the hex digits to be same width?
If you want to use a proportionally spaced font, you'll have to draw the characters at the appropriate places. Otherwise, find a monospaced font you like.
For completeness sake, I should post what I ended up doing after accepting what #AaronBratcher confirmed.
First I created 5 separate UILabels for the following elements:
leftTwoDigits-leftSeparator-centerTwoDigits-rightSeparator-rightTwoDigits
I used constrains to constrain all of their baselines, and have 0 horizontal spacing betwixt each. The separator labels were set simply to : and normal color. The xxxTwoDigits labels were given the string AA. A is NOT the widest character, C is. But A is near it. C is too wide, and A is wide enough. The color of these labels was set to clear so it doesn't actually show up.
Then 6 more UILabels are added. Again the the same baselines. The first two are constrained to the leading and trailing sides of leftTwoDigits and constrained to match in width. Repeat for the other 4 cells. Make all centered. And populate them individually with the individual digits.
For a UILabel of certain width, with certain font and font size, i want to calculate amount of characters that would make 7 lines worth of text inside it + ... (three dots showing continuation). Is there a fancier way to achieve this? Currently what i'm trying is counting up to X amount of characters or 7 new line characters, which ever comes first and i cut on the text right there.
More Detail:
Trying to make an expandable row Cell which contains the UILabel, i'm achieving this with auto layout... So to control the cell expansion, i change the text to be full text or a substring of that, with a button below which toggles between the string vs substring. All of that is working. The problem i'm getting is my method of finding the substring isnt very neat. Its not consistent on how it handles text of different combination of characters or newlines. I get variations of how it looks and sometimes it just ends with three dots on a new line rather than finishing on the 7th line.
Even using auto layout you can still use the lines property of UILabel to limit the number of lines displayed by the label.
So set it 7 and you'll get 7 or less rows. Just assign the complete text.
A multiline auto typing text box class (which uses an SKNode as the parent) is created using basically 2 elements:
an SKSpriteNode that acts as text box frame & background image/texture holder.
an NSMutableArray containing a set limited amount (rows) of NSStrings that each have a set character length.
After modifying this text box class so that it can be initialized with any frame width & height, I realized I didn't program the NSMutableArray to automatically change its content in a such way that it nicely fits within the background node (with a bit of padding involved as well). So here I am wondering how to do that since NSString's can only return the character count and not the width & height of each string in points (points could have maybe helped me create character constraints in some way).
Right now, the NSMutableArray uses a hardcoded maximum character count per NSString & a maximum row count for the entire array (it's 5 rows right now and when that limit is reached, a new "page"/array is created). This forces me to manually re-adjust these parameters every time I change the background node frame size which defeats the purpose of the class allowing the background frame to change.
Thing is, I'm trying to solve this in such a way that when I post this class on github, I want the solution to take into consideration any fontName & fontSize.
What are my options for solving this problem?
I've done something similar to this. It doesn't work 100% as to what you want, but should be similar enough. It uses a root node and from there, it will build multi-line text using an array of NSString which will in turn be used to build the SKLabelNode.
I'll outline what I did. I should also say I only run this when new text is set. In other words, I do not incur the penalty of deriving the information every frame. Only once.
The generalized steps are:
You will iterate over each character in the text string. Note I do this because my code supports word wrapping as well as other alignment capabilities. So for me, I want that level of control. As this is being done only upon creation, I'm fine with the overhead. If you don't want to word wrap you could always just create an array of words and work from there.
As you iterate over each character, you'll be generating an array of lines. Where each line in the array is a line that will fit in your frame. For now let's not worry about vertical constraints. So here we are primarily worried about width. For the current line, each character you are iterating over will get added to the current line. For this potential line string, you will use NSString's sizeWithAttributes, which is configured for your font. For example in my code it is an NSDictionary which contains: NSFontAttributeName : [UIFont fontWithName:self.fontName size:self.size]. This will be used to check the width, if that width exceeds the frame width, you are overrunning the line.
So the code may look something like:
size = [line sizeWithAttributes:attributes];
if (size.width > maxTextWidth) {
needNewline = YES;
}
If you have overrun a line, you need to determine if you are word wrapping. If you are, you can just add the current line (minus one character) to the lines array. If not you have prune off the last word in the current line and then add that to the array of lines.
The tricky parts are dealing with whitespace and handling non-word wrapped overflow. I have not addressed whitespace but you need to consider this very much in your code. Additionally, you also do want to factor in leading pixels, etc.
Once you have your array of lines, you can then create your children SKLabelNodes. I add them to the root, which allows me to move the group anywhere it needs to be.
The real key here is the lines array generation.
I have seen here people needing to calculate the size of the NSString given a size but I need to do the opposite.
Given a specified rect (or fixed UITextView, or multiline UILabel, no scrolling) I need to know:
if it managed to show all the chars of my NSString
if not, what is the last char shown
So that I can display the remaining text in another UITextView (of course if I could use a single UITextView I would not have this problem).
At first it seems a simple thing to do, but actually I am not finding a way, intuitively I think I could use either UITextView's:
textView.contentSize.height;
or NSString's:
sizeWithFont:constrainedToSize:lineBreakMode:
or a combination of the two, but I need to be precise and those methods do not help me in telling what is the last character that managed to fit the visible area of the UITextView.
Not sure if this is actually possible, but is a requirement of my client who thinks programming iOS is like printing a newspaper and expects to be able to format text around an image....
You could maybe get the maximum height of one line of text from a one character long string.
If you use that with sizeWithFont:constrainedToSize:lineBreakMode: then you should be able to know if your text runs onto more than one line (if the cgsize height is greater than the height of one line of text).
In order to find out the last character (or word) you would have to loop around the length of the string adding characters (or words) as you go and checking for when the cgsize height increases to add a new line, this will give you the character point when to split into multiple strings ( for multiple fields/labels/textviews ) or when to insert line breaks into the text ( if using a single multi-line textview or label ).
I hope you find an easier way...