NSLayoutManager drawGlyphsForGlyphRange:atPoint: - macros

How is the function drawGlyphsForGlyphRange:atPoint: in the class NSLayoutManager implemented? I know it's called by NSTextView and in turn calls fillBackgroundRectArray:count:forCharacterRange:color:
But how does it determine the backgroundcolor, if any, for the specified glyphrange?

You have another call for glyph background. You can use
- (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin;
UPDATE:
I use it like this in drawRect in UIView.
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
if (glyphRange.location != NSNotFound)
{
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:label.frame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:label.frame.origin];
}

Related

Delete Attributed Text When You Click on TextView

I'm having trouble figuring out how to delete attributed text when a user clicks on a textView, much like it does when you do on a textField. I know how to assign the attributed text, but I want it to disappear when a user clicks on the textView, rather than having to delete the attributed text themselves.
Here is the code I'm using to populate:
if([self.detailItem.comments.rootBeerComment isEqual: #""] || self.detailItem.comments.rootBeerComment == nil){
NSAttributedString *string = [[NSAttributedString alloc]initWithString:#"Notes..."];
self.rootBeerNotes.attributedText = string;
}else{
self.rootBeerNotes.text = self.detailItem.comments.rootBeerComment;
}
In order to do something when the textView is tapped, you need to know when this event happens. The easiest way is to set the delegate of the textView to your view controller, and then implement the UITextViewDelegate delegate method which tells you this:
- (BOOL)textViewShouldBeginEditing:(UITextField *)textView
{
// You may need to check that it is the right textView if you have more than one.
textView.attributedText = nil;
return YES;
}
As an aside, you can replace this line of code:
if([self.detailItem.comments.rootBeerComment isEqual: #""] || self.detailItem.comments.rootBeerComment == nil){
with this one, which is shorter and does the same thing since the length of an empty string and the length of a nil object will both return 0:
if([self.detailItem.comments.rootBeerComment length] == 0){
You can always use this. Make an NSRange that is from the first to the last character and use an empty string "".
myAttributedString.replaceCharacters(in range: NSRange, with str: String)

Is it possible to highlight a NSString or draw some color to a string

i am having this code to get the text between "." for example i am having lots of text like .1 this is first.2 this is second.3 this is fourth etc etc.when i tap the first ext it displays the first text in log .the code is
- (void)textViewDidBeginEditing:(UITextView *)textView
{
[NSTimer scheduledTimerWithTimeInterval:0.001 target:maintextview selector:#selector(resignFirstResponder) userInfo:nil repeats:NO];
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
NSRange selectedRange = [textView selectedRange];
NSString *backString = [maintextview.text substringToIndex:selectedRange.location];
NSRange backRange = [backString rangeOfString:#"." options:NSBackwardsSearch];
NSRange backRangee = [backString rangeOfString:#"." options:NSBackwardsSearch];
int myRangeLenght = backRangee.location - backRange.location;
NSRange myStringRange = NSMakeRange (backRange.location, myRangeLenght);
NSString *forwardString = [maintextview.text substringFromIndex:backRange.location];
NSLog(#"%#",[[forwardString componentsSeparatedByString:#"."] objectAtIndex:1]);
}
forwadString contains the tapped text,i just want to highlight this string or draw a color above this text using core graphics or something like that.is out possible?
thanks in advance
Much to my and many other's disappointment, Apple chose not to implement NSAttributedString until iOS 3.2, and even then, all standard UI elements are incapable of rendering them!
Luckily, the few, the proud, and the brave have answered the call and DTCoreText was born.
As for an actual selection, because UITextView conforms to UITextInput as of iOS 3.2, you can use and set the selectedTextRange.
It's impossible to 'colour' an NSString, a string is just a representation of characters, it holds no font, colour or style properties. Instead you need to colour the UI element that draws the text to the screen.
If forwardString is in a UILabel or UITextView you can colour the text inside these by setting the textColor property. For example if you had a UILabel called lbl you could set the colour by using:
lbl.textColor = [UIColor redColor];

Adding an uneditable text suffix to a UITextField

I have a UITextField that I'd like to add a "?" suffix to all text entered.
The user should not be able to remove this "?" or add text to the right hand side of it.
What's the best way to go about this?
Use the UITextFieldDelegate protocol to alter the string whenever the field is being edited. Here's a quick stab at it; this will need work, but it should get you started.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString * currentText = [textField text];
if( [currentText characterAtIndex:[currentText length] - 1] != '?' ){
NSMutableString * newText = [NSMutableString stringWithString:currentText];
[newText replaceCharactersInRange:range withString:string];
[newText appendString:#"?"];
[textField setText:newText];
// We've already made the replacement
return NO;
}
// Allow the text field to handle the replacement
return YES;
}
You'll probably need to subclass UITextField and override its drawText: method to draw an additional "?" character to the right of the actual text. (Rather than actually add a "?" to the text of the view.
I had this issue and I wrote a subclass to add this functionality: https://github.com/sbaumgarten/UIPlaceholderSuffixField.
Hopefully you have found a solution by now but if you haven't, this should work.
I realize this answer is late, but I found most of these did not work for my scenario. I have a UITextField that I simply want to force to have a suffix that the user cannot edit. However, I don't want to subclass UITextView, modify how it handles drawing, etc. I just want to prevent the user from modifying the suffix.
First, I ensure the suffix is set in the textfield when editing takes place. This could be done any number of ways depending upon your scenario. For mine, I wanted it there from the start, so I simply set the textfield's text property equal to the suffix when the view loads and store off the length of the suffix for later. For example:
- (void)viewDidLoad {
[super viewDidLoad];
myTextField.text = "suffix";
_suffixLength = myTextField.text.length;
}
Then I used the UITextFieldDelegate protocol as Josh suggested above, but use the length of the string and the range to ensure nothing edits the suffix:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// Determine starting location of the suffix in the current string
int suffixLocation = textField.text.length - _suffixLength;
// Do not allow replacing anything in/past the suffix
if (range.location + range.length > suffixLocation)
{
return NO;
}
// Continue with delegate code...
}
This should work for any suffix value you assign to the textfield.
For a single-line UITextField you should be able to measure the size of the NSString (it has a measurement function in there, somewhere) and move a UILabel to the right position.
I would add a method that is called when edit finishes:
`- (void)editDidFinish {
NSString* str=[[NSString alloc] init];
str=myEdit.text;
[str stringByAppendingString:#"?"];
myEdit.text=str;
}`
OK, im definitly too late, but maybe i can help someone out either way:
The intended way to accomplish this is by using a custom NSFormatter. Heres the docs:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSFormatter_Class/Reference/Reference.html
The basic idea is this: you create a subclass of NSFormatter, and the override at least the two worker methods:
-stringObjectForValue:
this will produce the dipsplay-String from the value stored in the object (i.e. add your questionmark here)
-objectValue:ForString:errorDescription
here, you need to transform the display-string into an object you want to store, i.e. remove the questionmark
The formatter can then be used to convert the data from the stored objects into strings that are suitable for presentation to the user.
The big advantage is that you can use formatters wherever your string will appear in the UI. It is not limited to certain UI-Elements like the solution where you override -drawText in UITextField. Its just hella convenient.
This class method I have written in Objective-C, helps you to add a suffix text to a UITextField.
I order to make it work, you need to initialize the UILabel to the prefix or suffix in your UITextFieldLabel as follow:
myTextField.rightView = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 0.0f, myTextField.frame.size.height)];
myTextField.rightViewMode = UITextFieldViewModeAlways;
[MyClass UpdateUITextFieldSuffix:myTextField withString:#"My Suffix!"];
Once we have the UILabel attached to the UITextField, you can use this class method to update the text, and this text will be automatically resized to fit in the field.
+ (BOOL)UpdateUITextFieldSuffix:(UITextField*)textField withString:(NSString*)string
{
BOOL returnUpdateSuffix = NO;
if (string != nil && [string respondsToSelector:#selector(length)] && [string length] > 0)
{
NSObject *labelSuffix = textField.rightView;
if (labelSuffix != nil && [labelSuffix respondsToSelector:#selector(setText:)])
{
[(UILabel*)labelSuffix setTextAlignment:NSTextAlignmentRight];
[(UILabel*)labelSuffix setText:string];
[(UILabel*)labelSuffix setBackgroundColor:[UIColor redColor]];
{
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
((UILabel*)labelSuffix).font, NSFontAttributeName,nil];
CGRect frame = [((UILabel*)labelSuffix).text boundingRectWithSize:CGSizeMake(0.0f, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributesDictionary
context:nil];
CGSize size = frame.size;
CGRect newFrame = [(UILabel*)labelSuffix frame];
newFrame.size.width = size.width;
[(UILabel*)labelSuffix setFrame:newFrame];
[(UILabel*)labelSuffix setNeedsLayout];
[(UILabel*)labelSuffix layoutIfNeeded];
}
returnUpdateSuffix = YES;
}
}
return returnUpdateSuffix;
}
I have written the following method to achieve the above task of placing non-editable suffix to UITextField:
- (void)setSuffixText:(NSString *)suffix
{
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
[label setBackgroundColor:[UIColor clearColor]];
[label setFont:[UIFont fontWithName:self.tfdDistance.font.fontName size:self.tfdDistance.font.pointSize]];
[label setTextColor:self.tfdDistance.textColor];
[label setAlpha:.5];
[label setText:suffix];
CGSize suffixSize = [suffix sizeWithFont:label.font];
label.frame = CGRectMake(0, 0, suffixSize.width, self.tfdDistance.frame.size.height);
[self.tfdDistance setRightView:label];
[self.tfdDistance setRightViewMode:UITextFieldViewModeAlways];
}​

How to setHighlightedTextColor # NSAttributedString

I have a custom UITableViewCell which uses a NSAttributedString. I want it to change color when the cell is selected. How can I make the NSAttributedString have the same behavior as a UILabel with highlightedTextColor set?
I have tried to change the color at the functions setSelected and setHighlighted of the cell, but it seems that they are called to late (on touchUpInside instead of touchDown)
Thanks in advance!
UILabel subclass solution
#implementation CustomLabelHighlighted
{
NSAttributedString *savedAttributedString;
}
-(void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
if (!highlighted)
{
[super setAttributedText:savedAttributedString];
return;
}
NSMutableAttributedString *highAttributedString = [savedAttributedString mutableCopy];
NSRange range = NSMakeRange(0, highAttributedString.string.length);
[highAttributedString addAttribute:NSForegroundColorAttributeName value:self.highlightedTextColor range:range];
[super setAttributedText:highAttributedString];
}
- (void)setAttributedText:(NSAttributedString *)attributedText
{
[super setAttributedText:attributedText];
savedAttributedString = attributedText;
}
#end
Typically it's pretty simple to detect selection/highlighting and change colors depending on it. The important methods are:
-(void)setHighlighted:animated:
-(void)setSelected:animated:
note that when overriding you have to use the methods with animated:, otherwise it won't work.
When you want to change only the color, the simplest solution is to let the color to be set on the label and not on the string. Note that the attributed string is still inheriting all the properties of the UILabel.

Disabling implicit animations in -[CALayer setNeedsDisplayInRect:]

I've got a layer with some complex drawing code in its -drawInContext: method. I'm trying to minimize the amount of drawing I need to do, so I'm using -setNeedsDisplayInRect: to update just the changed parts. This is working splendidly. However, when the graphics system updates my layer, it's transitioning from the old to the new image using a cross-fade. I'd like it to switch over instantly.
I've tried using CATransaction to turn off actions and set the duration to zero, and neither work. Here's the code I'm using:
[CATransaction begin];
[CATransaction setDisableActions: YES];
[self setNeedsDisplayInRect: rect];
[CATransaction commit];
Is there a different method on CATransaction I should use instead (I also tried -setValue:forKey: with kCATransactionDisableActions, same result).
You can do this by setting the actions dictionary on the layer to return [NSNull null] as an animation for the appropriate key. For example, I use
NSDictionary *newActions = #{
#"onOrderIn": [NSNull null],
#"onOrderOut": [NSNull null],
#"sublayers": [NSNull null],
#"contents": [NSNull null],
#"bounds": [NSNull null]
};
layer.actions = newActions;
to disable fade in / out animations on insertion or change of sublayers within one of my layers, as well as changes in the size and contents of the layer. I believe the contents key is the one you're looking for in order to prevent the crossfade on updated drawing.
Swift version:
let newActions = [
"onOrderIn": NSNull(),
"onOrderOut": NSNull(),
"sublayers": NSNull(),
"contents": NSNull(),
"bounds": NSNull(),
]
Also:
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
//foo
[CATransaction commit];
When you change the property of a layer, CA usually creates an implicit transaction object to animate the change. If you do not want to animate the change, you can disable implicit animations by creating an explicit transaction and setting its kCATransactionDisableActions property to true.
Objective-C
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
// change properties here without animation
[CATransaction commit];
Swift
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
// change properties here without animation
CATransaction.commit()
In addition to Brad Larson's answer: for custom layers (that are created by you) you can use delegation instead of modifying layer's actions dictionary. This approach is more dynamic and may be more performant. And it allows disabling all implicit animations without having to list all animatable keys.
Unfortunately, it's impossible to use UIViews as custom layer delegates, because each UIView is already a delegate of its own layer. But you can use a simple helper class like this:
#interface MyLayerDelegate : NSObject
#property (nonatomic, assign) BOOL disableImplicitAnimations;
#end
#implementation MyLayerDelegate
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
if (self.disableImplicitAnimations)
return (id)[NSNull null]; // disable all implicit animations
else return nil; // allow implicit animations
// you can also test specific key names; for example, to disable bounds animation:
// if ([event isEqualToString:#"bounds"]) return (id)[NSNull null];
}
#end
Usage (inside the view):
MyLayerDelegate *delegate = [[MyLayerDelegate alloc] init];
// assign to a strong property, because CALayer's "delegate" property is weak
self.myLayerDelegate = delegate;
self.myLayer = [CALayer layer];
self.myLayer.delegate = delegate;
// ...
self.myLayerDelegate.disableImplicitAnimations = YES;
self.myLayer.position = (CGPoint){.x = 10, .y = 42}; // will not animate
// ...
self.myLayerDelegate.disableImplicitAnimations = NO;
self.myLayer.position = (CGPoint){.x = 0, .y = 0}; // will animate
Sometimes it's convenient to have view's controller as a delegate for view's custom sublayers; in this case there is no need for a helper class, you can implement actionForLayer:forKey: method right inside the controller.
Important note: don't try to modify the delegate of UIView's underlying layer (e.g. to enable implicit animations) — bad things will happen :)
Note: if you want to animate (not disable animation for) layer redraws, it is useless to put [CALayer setNeedsDisplayInRect:] call inside a CATransaction, because actual redrawing may (and probably will) happen sometimes later. The good approach is to use custom properties, as described in this answer.
Here's a more efficient solution, similar to accepted answer but for Swift. For some cases it will be better than creating a transaction every time you modify the value which is a performance concern as others have mentioned e.g. common use-case of dragging the layer position around at 60fps.
// Disable implicit position animation.
layer.actions = ["position": NSNull()]
See apple's docs for how layer actions are resolved. Implementing the delegate would skip one more level in the cascade but in my case that was too messy due to the caveat about the delegate needing to be set to the associated UIView.
Edit: Updated thanks to the commenter pointing out that NSNull conforms to CAAction.
Actually, I didn't find any of the answers to be the right one. The method that solves the problem for me was this:
- (id<CAAction>)actionForKey:(NSString *)event {
return nil;
}
Then you can whatever logic in it, to disable a specific animation, but since I wanted to removed them all, I returned nil.
Based on Sam's answer, and Simon's difficulties... add the delegate reference after creating the CSShapeLayer:
CAShapeLayer *myLayer = [CAShapeLayer layer];
myLayer.delegate = self; // <- set delegate here, it's magic.
... elsewhere in the "m" file...
Essentially the same as Sam's without the ability to toggle via the custom "disableImplicitAnimations" variable arrangement. More of a "hard-wire" approach.
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
// disable all implicit animations
return (id)[NSNull null];
// allow implicit animations
// return nil;
// you can also test specific key names; for example, to disable bounds animation:
// if ([event isEqualToString:#"bounds"]) return (id)[NSNull null];
}
To disable implicit layer animations in Swift
CATransaction.setDisableActions(true)
Found out a simpler method to disable action inside a CATransaction that internally calls setValue:forKey: for the kCATransactionDisableActions key:
[CATransaction setDisableActions:YES];
Swift:
CATransaction.setDisableActions(true)
Updated for swift and disabling only one implicit property animation in iOS not MacOS
// Disable the implicit animation for changes to position
override open class func defaultAction(forKey event: String) -> CAAction? {
if event == #keyPath(position) {
return NSNull()
}
return super.defaultAction(forKey: event)
}
Another example, in this case eliminating two implicit animations.
class RepairedGradientLayer: CAGradientLayer {
// Totally ELIMINATE idiotic implicit animations, in this example when
// we hide or move the gradient layer
override open class func defaultAction(forKey event: String) -> CAAction? {
if event == #keyPath(position) {
return NSNull()
}
if event == #keyPath(isHidden) {
return NSNull()
}
return super.defaultAction(forKey: event)
}
}
Add this to your custom class where you are implementing -drawRect() method. Make changes to code to suite your needs, for me 'opacity' did the trick to stop cross-fade animation.
-(id<CAAction>) actionForLayer:(CALayer *)layer forKey:(NSString *)key
{
NSLog(#"key: %#", key);
if([key isEqualToString:#"opacity"])
{
return (id<CAAction>)[NSNull null];
}
return [super actionForLayer:layer forKey:key];
}
If you ever need a very quick (but admittedly hacky) fix it might be worth just doing (Swift):
let layer = CALayer()
// set other properties
// ...
layer.speed = 999
As of iOS 7 there's a convenience method that does just this:
[UIView performWithoutAnimation:^{
// apply changes
}];
To disable the annoying (blurry) animation when changing the string property of a CATextLayer, you can do this:
class CANullAction: CAAction {
private static let CA_ANIMATION_CONTENTS = "contents"
#objc
func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?) {
// Do nothing.
}
}
and then use it like so (don't forget to set up your CATextLayer properly, e.g. the correct font, etc.):
caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
You can see my complete setup of CATextLayer here:
private let systemFont16 = UIFont.systemFontOfSize(16.0)
caTextLayer = CATextLayer()
caTextLayer.foregroundColor = UIColor.blackColor().CGColor
caTextLayer.font = CGFontCreateWithFontName(systemFont16.fontName)
caTextLayer.fontSize = systemFont16.pointSize
caTextLayer.alignmentMode = kCAAlignmentCenter
caTextLayer.drawsAsynchronously = false
caTextLayer.actions = [CANullAction.CA_ANIMATION_CONTENTS: CANullAction()]
caTextLayer.contentsScale = UIScreen.mainScreen().scale
caTextLayer.frame = CGRectMake(playbackTimeImage.layer.bounds.origin.x, ((playbackTimeImage.layer.bounds.height - playbackTimeLayer.fontSize) / 2), playbackTimeImage.layer.bounds.width, playbackTimeLayer.fontSize * 1.2)
uiImageTarget.layer.addSublayer(caTextLayer)
caTextLayer.string = "The text you want to display"
Now you can update caTextLayer.string as much as you want =)
Inspired by this, and this answer.
Try this.
let layer = CALayer()
layer.delegate = hoo // Same lifecycle UIView instance.
Warning
If you set delegate of UITableView instance, sometimes happen crash.(Probably scrollview's hittest called recursively.)