How to setHighlightedTextColor # NSAttributedString - iphone

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.

Related

How can I set the selected text color of an NSTextField? [duplicate]

Trying to change the selected text background color for an NSTextField (we have a dark UI, and selected text background is almost the same as the text itself), but only NSTextView seems to allow us to change this.
So we're trying to fake an NSTextField using an NSTextView, but can't get text scrolling to work the same.
The closest we get is with this code:
NSTextView *tf = [ [ NSTextView alloc ] initWithFrame: NSMakeRect( 30.0, 20.0, 80.0, 22.0 ) ];
// Dark UI
[tf setTextColor:[NSColor whiteColor]];
[tf setBackgroundColor:[NSColor darkGrayColor]];
// Fixed size
[tf setVerticallyResizable:FALSE];
[tf setHorizontallyResizable:FALSE];
[tf setAlignment:NSRightTextAlignment]; // Make it right-aligned (yup, we need this too)
[[tf textContainer] setContainerSize:NSMakeSize(2000, 20)]; // Try to Avoid line wrapping with this ugly hack
[tf setFieldEditor:TRUE]; // Make Return key accept the textfield
// Set text properties
NSMutableDictionary *dict = [[[tf selectedTextAttributes] mutableCopy ] autorelease];
[dict setObject:[NSColor orangeColor] forKey:NSBackgroundColorAttributeName];
[tf setSelectedTextAttributes:dict];
This works almost alright, except that if the text is longer than the text field, you can't scroll to it in any way.
Any idea as how to accomplish this?
Thanks in advance
Edit: Solution suggested below by Joshua Nozzi
Thanks to Joshua, this is a great solution to what I was looking for:
#interface ColoredTextField : NSTextField
- (BOOL)becomeFirstResponder;
#end
#implementation ColoredTextField
- (BOOL)becomeFirstResponder
{
if (![super becomeFirstResponder])
return NO;
NSDictionary * attributes = [NSDictionary dictionaryWithObjectsAndKeys :
[NSColor orangeColor], NSBackgroundColorAttributeName, nil];
NSTextView * fieldEditor = (NSTextView *)[[self window] fieldEditor:YES forObject:self];
[fieldEditor setSelectedTextAttributes:attributes];
return YES;
}
#end
Instead of faking it with an NSTextView, it's just an NSTextField that changes the selected text color when it becomes first responder.
Edit: The above code falls back to the default selection color once you press Enter in the textfield. Here's a way to avoid that.
#interface ColoredTextField : NSTextField
- (BOOL)becomeFirstResponder;
- (void)textDidEndEditing:(NSNotification *)notification;
- (void)setSelectedColor;
#end
#implementation ColoredTextField
- (BOOL)becomeFirstResponder
{
if (![super becomeFirstResponder])
return NO;
[self setSelectedColor];
return YES;
}
- (void)textDidEndEditing:(NSNotification *)notification
{
[super textDidEndEditing:notification];
[self setSelectedColor];
}
- (void) setSelectedColor
{
NSDictionary * attributes = [NSDictionary dictionaryWithObjectsAndKeys :
[NSColor orangeColor], NSBackgroundColorAttributeName, nil];
NSTextView * fieldEditor = (NSTextView *)[[self window] fieldEditor:YES forObject:self];
[fieldEditor setSelectedTextAttributes:attributes];
}
#end
Why not just set the properties of the text field's field editor directly when the field becomes first responder?
In a typical text field, selection is only visible when the field is first responder, so if you ask for the text field's field editor, then set its selected text attributes, you'd get the same affect, wouldn't you?
NSDictionary * attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSColor orangeColor], NSBackgroundColorAttributeName, nil];
NSTextView * fieldEditor = (NSTextView *)[[self window] fieldEditor:YES
forObject:textField];
[fieldEditor setSelectedTextAttributes:attributes];
Note: As discussed in the comments below, the documentation is correct in saying the field editor is an instance of NSTextView, but the -[NSWindow fieldEditor:forObject:] method claims to return an NSText (NSTextView's immediate superclass). If you plan to send the field editor NSTextView-only methods, you'll need to cast it as an NSTextView to quiet the compiler's warnings. The casting won't break anything in your code, should the method prototype be corrected in the future, so it can safely be left in place.

Changing a view programmatically to align left or right

I have been building this chat and I am stuck in something that I can't figure out yet.
I have been unsuccessfully trying to align the same view left or right according to the scenario but I haven't gotten the desired result yet.
Basically, I built the cell in interface builder and pinned to the left and made its width and height variable according to the contents of the labels inside.
What I have been trying to do is reusing the same cell and align to the right when the I am the one sending the message (yellow) because by default it will be aligned to the left when others send me a message (gray) see the following image for illustration:
What I want is:
others messages--------
--------------my message
To accomplish this, and under TroyT's suggestion, I activated and deactivated the leading/trailing constraints according to my needs.
So what I did was.
On my UITableViewCell Class, I created two #IBoulets for my two constraints like so:
#IBOutlet var bubbleViewLeading: NSLayoutConstraint!
#IBOutlet var bubbleViewTrailing: NSLayoutConstraint!
Later, in my tableViewController on the method cellForRowAtIndexPAth , I activate or deactivate one of the constraints according to my needs doing the following.
cell.bubbleViewTrailing.active = true
cell.bubbleViewLeading.active = false
or the opposite
cell.bubbleViewTrailing.active = false
cell.bubbleViewLeading.active = true
where the constraint set to "True" is set to 0. As I mentioned before, this work well for all cells except the first one.
This works PARTIALLY, because for some reason it does not affect the first row and both constraints stay activated, hence the row is stretched through the tableview's width like so:.
I tried several things like:
Using the init method from my UITableViewCell class to change the constraints
Changing the constraints from the awakeFromNib method within my UITableViewCell
Using the "User Defined Runtime Attributes" on the 3rd tab from the left on the storyboard, add a key path named "active", set the type to Bool, and set the value to false.
unticking "installed" in the attribute inspector when I select the constraints in the storyboard
However, none of these methods affect the constraints on the first cell but I can easily change everything else like the cell background or the text alignment in a label within the cell. It just the constraint that will not change for that first time that I use the cell. Even when I scroll down past the boundaries of the screen to "force" the cell reuse, the first cell becomes fixed:
I have been trying to figure this one out for days with little success. Hence, I decided to put a bounty on this question.
What you explain makes little sense to me, but if it is happening only with the first row, then I would try forcing the layout to redraw by doing:
cell.setNeedsLayout()
cell.layoutIfNeeded()
from the cellForRowAtIndexPath
BTW, I think a better approach would be to register 2 different cells, and create them both in IB. This approach will keep your code shorter and will allow you to modify not only the trailing/leading but also colors, fonts or whatever you need. You give them 2 different IDs, but keep the same implementation class, and you just deque the one you need on your cellForRowAtIndexPath.
Keeping a different cell per style is the standard way to do these things with IB.
removeConstraint: is deprecated, in favor of using the active property instead (or NSLayoutConstraint's deactivateConstraints:). While removeConstraint: still usually works, combining that with setting your constraints' active property makes this worse. Either use all activate and deactivate (preferred) or use add and remove.
So instead of cell.removeConstraint:, use this:
cell.bubbleViewTrailing.active = false
One thing I may note is that your leading and trailing is reversed. Leading is on the left and trailing is on the right, except for right-to-left localization.
Here is the Custom ChatMessageCell that I have implemented for you in which I have applied programmatically constrains using the KVConstraintExtensionsMaster library to apply constraints that I have implemented.
I have update the leading & trailing constraints Constant value instead of activate/deactivate constraint or remove/add constraint.
I hope this may help you.
Put below code in ChatMessageCell.h header file
typedef NS_ENUM(NSUInteger, Type) {
TypeSender,
TypeReceiver,
};
#interface ChatMessageCell : UITableViewCell
#property (assign, nonatomic) Type cellType;
#property (strong, nonatomic) UIView *msgBackgroundView;
#property (strong, nonatomic) UILabel *messageLabel;
#end
Put below code in ChatMessageCell.m file
#import "ChatMessageCell.h"
#import "KVConstraintExtensionsMaster.h"
#implementation ChatMessageCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self createAndConfigureViewHierarchy];
}
return self;
}
- (void)createAndConfigureViewHierarchy
{
_msgBackgroundView = [UIView prepareNewViewForAutoLayout];
[self.contentView addSubview:_msgBackgroundView];
_msgBackgroundView.backgroundColor = [UIColor clearColor];
[_msgBackgroundView.layer setCornerRadius:6.0];
_labelMessage = [UILabel prepareNewViewForAutoLayout];
[_labelMessage setLineBreakMode:NSLineBreakByTruncatingTail];
[_labelMessage setNumberOfLines:0];
[_labelMessage setTextAlignment:NSTextAlignmentLeft];
[_labelMessage setTextColor:[UIColor whiteColor]];
self.labelMessage.backgroundColor = [UIColor clearColor];
[_msgBackgroundView addSubview:_labelMessage];
[self applyConstraints];
}
-(void)applyConstraints
{
// now applying the constraints by using KVConstraintExtensionsMaster library
CGFloat padding = 8.0;
// adding Top and Bottom contraints of _msgBackgroundView
[_msgBackgroundView applyTopAndBottomPinConstraintToSuperviewWithPadding:padding];
// adding leading and trailing contraints of _msgBackgroundView
[_msgBackgroundView applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:padding];
// adding Top and Bottom contraints of _msgBackgroundView
[_labelMessage applyTopAndBottomPinConstraintToSuperviewWithPadding:padding];
// adding leading and trailing contraints of _msgBackgroundView
[_labelMessage applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:padding];
}
-(void)setCellType:(Type)cellType
{
switch (cellType) {
case TypeSender:
{
[_msgBackgroundView setBackgroundColor:[UIColor redColor]];
[_labelMessage setTextAlignment:NSTextAlignmentRight];
// this method will change the Leading Pin Constraint Constant value by 100.0
[_msgBackgroundView applyLeadingPinConstraintToSuperviewWithPadding:100.0];
// this method will increase the Leading Pin Constraint Constant value with proper ratio only iPad
[_msgBackgroundView updateAppliedConstraintConstantValueForIpadByAttribute:NSLayoutAttributeLeading];
break;
}
case TypeReceiver:
{
[_msgBackgroundView setBackgroundColor:[UIColor redColor]];
[_labelMessage setTextAlignment:NSTextAlignmentLeft];
// this method will change the Trailing Pin Constraint Constant value by 100.0
[_msgBackgroundView applyTrailingPinConstraintToSuperviewWithPadding:100.0];
// this method will increase the Leading Pin Constraint Constant value with proper ratio only iPad
[_msgBackgroundView updateAppliedConstraintConstantValueForIpadByAttribute:NSLayoutAttributeTrailing];
break;
}
}
[self.contentView setNeedsLayout];
[self.contentView updateModifyConstraints];
}
-(void)prepareForReuse
{
// this method will change the Leading And Trailing Pin Constraints Constant value by 8.0
[_msgBackgroundView applyLeadingAndTrailingPinConstraintToSuperviewWithPadding:8.0];
[super prepareForReuse];
}
#end
Put the below code in the viewDidLoad method ofyour ViewController is:
[self.tableView registerClass:ChatMessageCell.class forCellReuseIdentifier:#"KVChatMessageCell"];
self.tableView.rowHeight = UITableViewAutomaticDimension;
/* any estimated height but must be more than 2 */
self.tableView.estimatedRowHeight = 44.0;
Now implement UITableView DataSource in your ViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section{
return messages.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"KVChatMessageCell";
ChatMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell.labelMessage setText:messages[indexPath.row]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (indexPath.row%2 == 0) {
// here all even cell are the sender type
[cell setCellType:TypeSender];
}
else {
// here all odd cell are the Receiver type
[cell setCellType:TypeReceiver];
}
return cell;
}

Can I reuse colors in Interface Builder?

I've got several yellow buttons created using Inteface Builder. All have the same color. Currently I declare color in each xib. Can I declared it globally and reuse across all xibs?
Not possible in Interface Builder. Do it in code, for example by creating special subclass of the button.
You could use system Color Palette to save the color, but you still need to apply it to all buttons every time you decide to change it. Or you can just use Recently Used Colors in the color chooser, but neither way is enough dynamic.
Yes, you can do this.
At the bottom of the color picker popup in Interface Builder, there's a row of squares you can use to store colors for later use. Drag a color into it from the rectangle where the current color is shown at the top of the color picker to store it, and then just click a stored color later to use it.
I don't believe there is a way to do this entirely in interface builder, unfortunately. However, you can come pretty close with a little bit of code. The best way I've found to be able to change colors throughout the app in one go is to subclass the item that you want to color (UILabel, for instance) to set the color upon initialization:
#interface HuedUILabel : UILabel
#end
#implementation HuedUILabel
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code
self.textColor = [AppConfig primaryColor];
}
return self;
}
#end
Then, set the label to have a custom class in IB:
Now, when you want to change the color of all your UILabels, you can do it by changing your one color def AND you don't have to clutter your code with a bunch of appearance assignments.
Most definitely!
Create a singleton object (you can call it OksanaColor, to be cool)...
... or, if you're really lazy, a read-only UIColor property that you can access from your app delegate.
You can also add a category on UIColor, so you can use it same as when you use UIColor. For example in my App I add a new file called ApplicationColors which contains all my app colors.
#interface UIColor (ApplicationColours)
+(UIColor *)savaColor;
#end
Implementation:
#implementation UIColor (ApplicationColours)
+(UIColor *)savaColor
{
return [UIColor colorWithRed:228.0f/255.0f green:86.0f/255.0f blue:86.0f/255.0f alpha:1.0f];
}
#end
Then to use it in my app, I import the ApplicationColours.h and use the same as any other UIColor. i.e:
label.textColor = [UIColor savaColor];
Here's a very simple implementation of a named colors category for UIColor. With this code in your project, UIColor will remember any colors you want to save, and will let you access your own colors or system colors using +colorWithName:
#interface UIColor (namedColors)
+ (UIColor *) colorWithName:(NSString *) name;
+ (void) setColor:(UIColor *) color forName:(NSString *) name;
#end
static NSMutableDictionary *colorStorage;
#implementation UIColor (namedColors)
+ (NSMutableDictionary *) colorStorage
{
if (!colorStorage)
colorStorage = [[NSMutableDictionary alloc] initWithCapacity:10];
return colorStorage;
}
+ (UIColor *) colorWithName:(NSString *) name
{
UIColor *result =[[self colorStorage] valueForKey:name]; // See if we have a color with this name in the colorStorage.
if (result) return result;
SEL selector = NSSelectorFromString(name); // look for a class method whose selector matches the given name, such as "blueColor" or "clearColor".
if ([self respondsToSelector:selector] && (result = [self performSelector:selector]))
if ([result isKindOfClass:[self class]])
return result;
return nil;
}
+ (void) setColor:(UIColor *) color forName:(NSString *) name
{
[[self colorStorage] setValue:color forKey:name];
}
#end

Custom UILabel creation to avoid localization problems

Dear programmers,
I am creating a customLabel class like below:
#interface CustomLabel : UILabel {
NSString *customBundlePath;
}
#implementation CustomLabel
- (void)drawTextInRect:(CGRect)rect
{
NSString *result=[self getLocalvalue:self.text];
[result drawInRect:rect withFont:self.font];
}
-(void)awakeFromNib
{
NSLog(#"%#",self.text);
}
-(NSString *)getLocalvalue:(NSString*)textTolocalize
{
// some code
return localizedText;
}
But my problem is, drawTextInRect method calling only once for a Label at the time of nib loading.
If view is Appearing again by popig, then which method will execute for every customLabel object ?
Please help me out.
Thanks, in advance.
You don't need custom classes.
NSString *someTextString = NSLocalizedString(#"SomeText", #"Description of text for translators")
[myLabel setText:someTextString];
Then you can extract the strings from your files and provide proper localization texts.
A couple of useful links:
http://www.icanlocalize.com/site/tutorials/iphone-applications-localization-guide/
http://www.raywenderlich.com/2876/how-to-localize-an-iphone-app-tutorial

Is it possible to force value in property setter?

What I want is to override UINavigationBar tintColor setter and force default color to it. Bellow is my code (which of course fails). Is there a way to make it work?
#implementation UINavigationBar (UINavigationBarCategory)
- (void)setTintColor:(UIColor *)tint {
self.tintColor = [UIColor greenColor];
}
#end
(I'm using property as a generic term here, in your example it's a stand-in for tintColor)
I don't think you can use the self.property = syntax for assignment inside a setProperty: method. self.property is just an alias for [self setProperty:<value>] and it will recursively call itself.
You'll have to do something like this:
- (void)setProperty:(id)pProperty {
[property autorelease];
property = [pProperty retain];
}
I'm still not 100% sure what you're trying to do will work, but the preceding is a start.
If you want to force a default color of a NavigationBar, why don't you set the tint color in viewDidLoad/viewDidAppear of your view controller??
self.navigationController.navigationBar.tintColor = [UIColor (color you want)];