I've been looking for a solution to this problem for a while, and no one seems to have come across a similar issue.
Basically I have multiple UITextViews that I use to detect addresses, urls, phone numbers, etc (anything that can be detected via UIDataDectorTypeAll) from some EKEvent.notes. I then add these UITextViews as subviews of a UIScrollView.
Now, for some reason or another, once the UITextView detects an address or a phone number and becomes an actionable target, it will randomly draw with a font 2x its specified font!
I've setup tests to just redraw my views if I tap. When the UITextView is added to the view initially, I can see in black the proper text. Then it does its detection deal and becomes a actionable target. Sometimes it stays the proper size, sometimes it draws at 2x font (but still in proper frame, thus it gets clipped).
It's very straight forward, but here's my code below. All variable are correct values, frame is correct, text is correct, everything is correct and about 50% of the time it draws correct. Its just that other 50% of the time it becomes (apparently) 2x font! Any help is greatly appreciated!
UITextView *locationTextView = [[UITextView alloc] init];
locationTextView.dataDetectorTypes = UIDataDetectorTypeAll;
locationTextView.text = location;
locationTextView.font = [UIFont fontWithName:#"AvenirNext-Regular" size:17];
locationTextView.editable = NO;
locationTextView.userInteractionEnabled = YES;
locationTextView.contentInset = UIEdgeInsetsMake(-8,-8,-8,-8);
locationTextView.frame =CGRectMake(kBufferLeft, daySize.height, kBufferDayViewTextWidth, locationSize.height);
[scrollView addSubview:locationTextView];
Correct: http://i.imgur.com/3pJ43kj.jpg
Incorrect: http://i.imgur.com/DLq4gco.jpg
(Not allowed to post images yet, sorry.)
Same exact code produced both effect. Thank you for your time.
Cheers!
EDIT: I went with TTTAttributedLabels to fix this issue.
github.com/mattt/TTTAttributedLabel
You can set font at <UITextField> delegate.
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
locationTextView.font = [UIFont fontWithName:#"AvenirNext-Regular" size:17];
}
I had the same problem because I was using a custom line breaking (layoutManager:shouldBreakLineByWordBeforeCharacterAtIndex:). Had to disable that.
Related
I need to show a tilt of the device in my app. The algorithm is pretty simple, I'm using CMMotionManager's attitude for calculating tilt, and I'm updating a label which shows degrees like so:
- (void)tiltUpdated:(float)tilt
{
_degreesLabel.text = [NSString stringWithFormat:#"%.1f°", tilt];
}
My problem is next - CMMotionManager calls gyroscope updates approximately 10 times per second. And every time I'm calculating new tilt and calling tiltUpdated method each time. And when I do that, my app starts to incredibly lag. Few things I need to clarify:
Cause of lags is in updating the label. I defined it pretty easily
by commenting on it. So it's not the tilt calculations (that is why I
didn't provide a code for that here)
Applications also show camera output all the time. I turned off
camera and things got a little better but still, the application is lagging.
Is there any way to optimize updating UILabel text? Thanks in advance!
Ok, ending up answering my own question, but I hope that'll come in handy for somebody :).
I didn't find any valuable and detailed information about UILabel performance (which is too bad because I'm interested to learn something about that), but I found an alternative, which is CATextLayer.
I'm creating CATextLayer:
_textLayer = [CATextLayer new];
_textLayer.frame = CGRectMake(0.0f, _degreesLabelView.frame.size.height*0.5f - textHeight*0.5f, _degreesLabelView.frame.size.width, textHeight);
_textLayer.backgroundColor = [UIColor clearColor].CGColor;
_textLayer.foregroundColor = [UIColor whiteColor].CGColor;
_textLayer.alignmentMode = kCAAlignmentCenter;
_textLayer.contentsScale = UIScreen.mainScreen.scale;
_textLayer.fontSize = 17.0;
_textLayer.string = #"0°";
[_degreesLabelView.layer addSublayer:_textLayer];
And here I'm updating text:
- (void)tiltUpdated:(float)tilt
{
_textLayer.string = [NSString stringWithFormat:#"%.1f°", tilt];
}
Also CATextLayer is kind of "animating" text changes with slight "fadeIn/fadeOut" and I like it :)
I have been using a method of resizing my UIButtons that is depreciated, and not very robust. For that and also other reasons, I want to get sizeThatFits to work for UIButtons. From what I've read online, I'm not sure if it should work (seems like it is working for some, but not others, the difference maybe between the style, I'm using custom).
Here is my simple test code to recreate the issue (I just put this in viewDidLoad to test, but shouldn't matter, and my real code is part of a large project):
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setTitle:#"This is a test with a long title that will need to word wrap to more than a single line when displayed on my tiny iPod in portrait view." forState:UIControlStateNormal];
btn.titleLabel.lineBreakMode = UILineBreakModeWordWrap; // depreciated - but nothing to replace it?
CGRect r = btn.frame;
r.origin.x = 0;
r.origin.y = 0;
r.size.width = 320;
r.size.height = [btn.titleLabel.text sizeWithFont:btn.titleLabel.font constrainedToSize:CGSizeMake(r.size.width,100000) lineBreakMode:btn.titleLabel.lineBreakMode].height; // Returns approx 86 and changes correctly if I change the title text
r.size.height = [btn sizeThatFits:CGSizeMake(r.size.width,CGFLOAT_MAX)].height; // returns 34 no matter what
btn.frame = r;
The sizeWithFont line is what I have been doing, and it works, but it isn't asking the actual control for the size, so not really safe and has been depreciated also. The sizeThatFits line is what I would like to get working, but it always returns 34 no matter what (probably the recommended/default height of a button).
I've been using the same sizeWithFont to resize UILabels and some other controls as well. I've updated them to use sizeThatFits and they work great, just UIButton isn't working the same as the others. I'm hoping there is a simple fix, like setting a property of the UIButton, to get this working?
My app only needs to support iOS 8+, not older versions.
Update: Based on the comments here How do I resize a UIButton to fit the text without it going wider than the screen? and the accepted answer, it seems like we might be stuck with sizeWithFont or other sub-par solutions... dang.
I fixed it using UIButton titleLabel instead of UIButton.
button.titleLabel?.sizeThatFits(labelFitSize)
Instead of
button.sizeThatFits(labelFitSize)
Good news is that it works, bad news you will need to handle yourself if the button as contentInsets or images...
UITextField in iOS 8.1.X have different vertical text alignment between editing and not editing mode when using some fonts like Helvetica Neue Light 17:
I have a sample project here.
Is there a workaround that I don't need to create a custom text field?
The main problem is that when using Dynamic Type, so the font can changes in runtime.
Anyway I opened a radar rdar://19374610.
Just stumbled across the same issue and decided to 'debug' a little. Basically I just plotted values for various fonts (cap height, point size, preferred height, available height, the distance the text moved) and noticed a pattern.
The reason the text moves up because it is rendered in two completely different ways: the non-editing version is rendered using -drawRect: (you can even override the hook), the editing version is rendered by a so-called UIFieldEditor. This one appears to ceil the text height regardless of whether or not you're on a Retina device and centers it afterwards. On Retina devices though, you should always ceil(scalar * scale) / scale to align on pixels. Hence iOS assumes a greater text height than needed, and moves it a little further up to keep it centered. Funnily enough, the rendering of static text and UIFieldEditor differ.
To fix the issue, subclass UITextField and override -editingRectForBounds:. Here you will want to take the non-editing-rect ('text rect') and account for the shift Apple is going to perform in advance.
- (CGRect)editingRectForBounds:(CGRect)bounds
{
if (UIDevice.currentDevice.systemVersion.integerValue != 8) return [self textRectForBounds:bounds];
CGFloat const scale = UIScreen.mainScreen.scale;
CGFloat const preferred = self.attributedText.size.height;
CGFloat const delta = ceil(preferred) - preferred;
CGFloat const adjustment = floor(delta * scale) / scale;
CGRect const textRect = [self textRectForBounds:bounds];
CGRect const editingRect = CGRectOffset(textRect, 0.0, adjustment);
return editingRect;
}
Edit: I just tested the code on older OS versions, including 8.0. On iOS 7.x, everything appears to be fine, iOS 8.0 contains the bug already. We cannot predict the future, so for now I would only include the fix for iOS 8.x, hopefully Apple fixes the problem in iOS 9 themselves.
Another Edit: This code makes the editing text appear at the same location as its static counterpart. If you want to control them separately (which Apple thinks makes sense, since they offer both -textRectForBounds: and -editingRectForBounds:), you may want to replace [self textRectForBounds:bounds] with [super editingRectForBounds:bounds]. If you want to implement this fix in a category using swizzling, you certainly should use the super version.
This bug is font dependant so I will go with a solution that set the style of the attributed string to compensate this.
An example to fix this for the first textField with the Helvetica Neue Light and size 17:
Suppose the view controller is the delegate of the firstTextField
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSDictionary *style = #{
NSFontAttributeName : [UIFont fontWithName:#"HelveticaNeue-Light"
size:17],
NSParagraphStyleAttributeName : [NSParagraphStyle defaultParagraphStyle],
NSBaselineOffsetAttributeName: #(0.4)
};
_firstTextField.attributedText = [[NSAttributedString alloc] initWithString:_firstTextField.text
attributes:style];
}
-(void)textFieldDidBeginEditing:(UITextField *)textField {
NSDictionary *style = #{
NSFontAttributeName : [UIFont fontWithName:#"HelveticaNeue-Light"
size:17],
NSParagraphStyleAttributeName : [NSParagraphStyle defaultParagraphStyle],
NSBaselineOffsetAttributeName: #(0.0)
};
[_firstTextField setTypingAttributes: style];
}
The key part is when you set the NSBaselineAlignment from when you present the textField to when you are editing it. Also I checked what happen when the font is double size (with a textField big enough) and the NSBaselineOffset is still the same to avoid the jumping effect.
I have a UITextField that is using the Museo Sans Rounded 300 font. Everything works fine for normal UITextFields, but when you set the secureTextEntry = YES, then there's this disconcerting change to the size of the bullets as the UITextField gets and loses focus (i.e. becomes, and relinquishes, being the first responder).
When the UITextField has focus, the bullets appear to be using the custom font, but once it loses focus they change to being these much bigger (standard size) bullets.
So, the only way I found to combat this was to use the textFieldDidBeginEditing and textFieldDidEndEditing delegate methods, keep track of what was entered in the text field, replace it with a mask of bullets, and disable secureTextEntry. So, when they leave the field, they’re actually just seeing the right number of bullets, rather than their secured text. It’s hacky and messy, but it’ll do for me, perhaps for you, too.
I found an easy solution an it works quite good.
Basically you have to change the font to a custom font when you set secureTextEntry to yes.
- (void)textFieldDidBeginEditing:(UITextField *)textField{
if([textField.text isEqual:#"Password"]){
textField.text = #"";
textField.font = [UIFont fontWithName:#"Helvetica" size:14.5];
textField.secureTextEntry = YES;
}
}
- (void)textFieldDidEndEditing:(UITextField *)textField{
if([textField.text isEqual:#""]){
textField.text = #"Password";
textField.secureTextEntry = NO;
textField.font = [UIFont fontWithName:#"YourFont" size:14.5];
}
}
Another workaround:
While this is an iOS bug (and new in iOS 7, I should add), I do have another way to work around it that one might find acceptable. The functionality is still slightly degraded but not by much.
Basically, the idea is to set the font to the default font family/style whenever the field has something entered in it; but when nothing is entered, set it to your custom font. (The font size can be left alone, as it's the family/style, not the size, that is buggy.) Trap every change of the field's value and set the font accordingly at that time. Then the faint "hint" text when nothing is entered has the font that you want (custom); but when anything is entered (whether you are editing or not) will use default (Helvetica). Since bullets are bullets, this should look fine.
The one downside is that the characters, as you type before being replaced by bullets, will use default font (Helvetica). That's only for a split second per character though. If that is acceptable, then this solution works.
i just test result above, #Javier Peigneux's answer is the most concise
#pragma mark -- UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UCSSafeTF *)safeTF{
safeTF.font = [UIFont fontWithName:#"Helvetica" size:14];
}
- (void)textFieldDidEndEditing:(UCSSafeTF *)safeTF{
safeTF.font = [UIFont fontWithName:#"Helvetica" size:14];
}
now i write like this, and the result is OK. then the reason why you see the bullets size change from small to big is very clear, just because apple iOS 10 below "help" us resize the custom font. hope will help you .
Just create a method that gets called every time the show/hide password toggle is selected. Inside the method, set the font to nil, then set the font to your custom font and font size. You should be setting the custom font and size in the viewWillAppear method as well. Inside this method, you're re-setting it.
This way, you don't need to disable secureTextEntry(which could make your text field vulnerable) and you don't need to use textFieldDidBeginEditing or textFieldDidEndEditing.
Code Example:
//if the password is obscured and the toggle to show it has been turned on, display password. Else, obscure it.
- (IBAction)togglePasswordVisibility:(id)sender {
// Xcode contains a bug where the font changes to default font if these two lines of code are not included.
self.passwordInputTextField.font = nil;
self.passwordInputTextField.font = [UIFont fontWithName:#"myCustomFontName" size:myDesiredFontSize]; //set this in viewWillAppear as well!
if (self.passwordInputTextField.secureTextEntry == YES) {
self.passwordInputTextField.secureTextEntry = NO;
[self.showHideButton setTitle:#"HIDE" forState:UIControlStateNormal];
} else {
self.passwordInputTextField.secureTextEntry = YES;
[self.showHideButton setTitle:#"SHOW" forState:UIControlStateNormal];
}
}
I am trying to determine the precise position of a character in a UILabel, say:
(UILabel *)label.text = #"Hello!";
I'd like to determine the position of the 'o'. I thought that I could just sum the widths of all the preceding characters (or the whole preceding string) using sizeWithFont. The width value I get though is bigger by about 10% than what it should be. Summing the widths of individual letters (i.e. [#"H" sizeWithFont...] + [#"e" sizeWithFont...] + l... + l...) accumulates more error than [#"Hell" sizeWithFont...].
Is there a way of accurately determining the position of a single glyph in a string?
Many thanks.
Yes, but not in a UILabel and not using sizeWithFont:.
I recently worked with Apple Developer Support, and apparently sizeWithFont: is actually an approximation. It becomes less accurate when your text (1) wraps across multiple lines and (2) contains non-latin characters (i.e. Chinese, Arabic), both of which cause line spacing changes not captured by sizeWithFont:. So, don't rely on this method if you want 100% accuracy.
Here are two things you can do:
(1) Instead of UILabel, use a non-editable UITextView. This will support the UITextInput protocol method firstRectForRange:, which you can use to get the rect of the character you need. You could use a method like this one:
- (CGRect)rectOfCharacterAtIndex:(NSUInteger)characterIndex inTextView:(UITextView *)textView
{
// set the beginning position to the index of the character
UITextPosition *beginningPosition = [textView positionFromPosition:textView.beginningOfDocument offset:characterIndex];
// set the end position to the index of the character plus 1
UITextPosition *endPosition = [textView positionFromPosition:beginningPosition offset:1];
// get the text range between these two positions
UITextRange *characterTextRange = [textView textRangeFromPosition:beginningPosition toPosition:endPosition]];
// get the rect of the character
CGRect rectOfCharacter = [textView firstRectForRange:characterTextRange];
// return the rect, converted from the text input view (unless you want it to be relative the text input view)
return [textView convertRect:rectOfCharacter fromView:textView.textInputView];
}
To use it, (assuming you have a UITextView called myTextView already on the screen), you would do this:
myTextView.text = #"Hello!";
CGRect rectOfOCharacter = [self rectOfCharacterAtIndex:4 inTextView:myTextView];
// do whatever you need with rectOfOCharacter
Only use this method for determining the rect for ONE character. The reason for this is that in the event of a line break, firstRectForRange: only returns the rect on the first line, before the break.
Also, consider adding the method above as a UITextView category if you're gong to be using it a lot. Don't forget to add error handling!
You can learn more about how firstRectForRange: works "under the hood" by reading the Text, Web, and Editing Programming Guide for iOS.
(2) Create your own UILabel by subclassing UIView and using Core Text to render the strings. Since you're doing the rendering, you'll be able to get the positions of characters. This approach is a lot of work, and only worthwhile if you really need it (I, of course, don't know the other needs of your app). If you aren't sure how this would work, I suggest using the first approach.
Well fonts are smart now a day and take in respect the position of a character to its pervious character.
Here is an example on how the starting position of the letter o:
NSRange posRange = [hello rangeOfString:#"o"];
NSString *substring = [hello substringToIndex:posRange.location];
CGSize size = [substring sizeWithFont:[UIFont systemFontOfSize:14.0f]];
No you can do the same for the string including the letter o and substract the size found in the string without the letter o.
THis should give the an nice start position of the letter and the size.
in ios6 you can do using attributed string
NSMutableAttributedString *titleText2 = [[NSMutableAttributedString alloc] initWithString:strHello];
NSRange posRange = [hello rangeOfString:#"o"];
[titleText2 addAttributes:[NSDictionary dictionaryWithObject:[UIFont systemFontOfSize:14.0f] forKey:NSFontAttributeName] range:NameRange];
and set your textView with this attributed string