iOS Creating attributed label like TTTAttributedLabel in CKLabelComponent - ios

I can easily use TTTAttributedlabel to have tappable url, name, etc.
1) How can I create something similar to that in CKLabelComponent?
2) Do I need to use CKTextComponent?
[CKLabelComponent newWithLabelAttributes:{
.string = #"This shall be context This shall be context This shall be context This shall be context",
.font = [UIFont fontWithName:#"Baskerville" size:14]
}
viewAttributes:{
{#selector(setBackgroundColor:), [UIColor clearColor]},
{#selector(setUserInteractionEnabled:), #NO},
}
size:{ }]

You could make you component easily. Provide the view, implement the computeLayoutThatFits:. CKImageComponent is an example.
But at the situation of text. It may be hard, because of the layout size. Once you use TTTAttributedLabel as the render of text, you have to provide the text size manually. This is definitely not the thing you want.
As you could see, CKTextComponent, the parent of CKLabelComponent, is implemented as a subproject of ComponentKit. It handle the layout size, text rendering, text layout cache. (It's implemented with TextKit.) If you want to use TTTAttributedLabel, you have to handle all things yourself with TTTAttributedLabel. It may slow down your scroll, because CKTextComponent implement async render but TTTAttributedLabel don't.
CKTextKitEntityAttributeName may achieve your goal

My way is to combine CK with TTTAttributedLabel. First, I define a struct, it contains some basic info I need to set to TTTAttributedLabel,
struct ABCKAttributeLabelData {
NSString *content;
UIFont *normalFont;
UIColor *normalColor;
UIFont *linkFont;
UIColor *linkColor;
NSInteger numberOfLines;
};
Then, I create a new class, named ABCKAttributeLabelComponent subclass of CKComponent, implemented initialize method as below:
+ (instancetype)newWithData:(ABCKAttributeLabelData)data {
ABCKAttributeLabelComponent *com =
[super newWithView:{
[TTTAttributedLabel class],
{
{#selector(setText:), [data.content attributedStringWithStyle:
#{NSForegroundColorAttributeName : data.normalColor,
NSFontAttributeName : data.normalFont,}]},
{#selector(setLinkAttributes:), #{ NSForegroundColorAttributeName : data.linkColor,
NSFontAttributeName : data.linkFont}},
{#selector(setActiveLinkAttributes:), #{ NSForegroundColorAttributeName : data.linkColor,
NSFontAttributeName : data.linkFont}},
{#selector(setNumberOfLines:), data.numberOfLines ?: 0},
{#selector(setLineBreakMode:), NSLineBreakByTruncatingTail},
}
} size:{}];
com.attributeString = [data.content attributedStringWithStyle:
#{NSForegroundColorAttributeName : data.normalColor,
NSFontAttributeName : data.normalFont,}];
com.normalFont = data.normalFont;
com.numOfLine = data.numberOfLines ?: 0;
return com
Third, calculate TTTAttributedLabel's size and return. CK will call computeLayoutThatFits: method to get component size. So overwrite it.
- (CKComponentLayout)computeLayoutThatFits:(CKSizeRange)constrainedSize {
// self.attributeString,self.numOfLine and self.normalFont I saved as property in initialize method
CGSize computerSize = [self.attributeString sizeLabelToFitToSize:constrainedSize.max numberLines:self.numOfLine font:self.normalFont];
return {
self,
constrainedSize.clamp({
CKCeilPixelValue(computerSize.width),
CKCeilPixelValue(computerSize.height)
}),
{}
};
}
The rest of the things is to use ABCKAttributeLabelComponent.
it may not be very elegance, but it can works.

Related

Accessing "text styles" in Interface Builder and/or Storyboards

In one of my apps I have a styles document with methods for different text styles, for example:
+(UIFont*)h1{
return [UIFont fontWithName:#"Helvetica" size:48.0];
}
Then, in the viewDidLoad methods of each my view controllers, I set the text styles programmatically. It's been a really great way to keep styles across the app consistent and easy to tweak.
Here's my question: is there any way to have the XIB files/Storyboards reflect these text styles? If not, is there any way to implement similar functionality (i.e., have all the styles defined in one place, and have the XIB/Storyboard elements pull from there)? Thanks for reading.
EDIT:
To clarify, here's the desired end-result:
Define some constant text styles like h1, h2, p somewhere in the project. Each text style has its own font, font size, colour, and so on.
Be able to set the style of UILabels in my various views to any of these text styles. This could be done in code, in Interface Builder (e.g., in User Defined Runtime Attributes as Luan suggested), or wherever.
Be able to see the styles applied to each UILabel in Interface Builder / Storyboard without having to run the app every time.
OK, so it turns out this is possible to do! Here's how:
Add a styles class, where you can put all your style info in one place:
import UIKit
class MyStyles: NSObject {
static func fontForStyle(style:String)->UIFont{
switch style{
case "p":
return UIFont.systemFontOfSize(18);
case "h1":
return UIFont.boldSystemFontOfSize(36);
case "h2":
return UIFont.boldSystemFontOfSize(24);
default:
return MyStyle.fontForStyle("p");
}
}
}
Make a subclass of any objects you'd like to implement the styles, say UILabel, and enter the following code:
import UIKit
#IBDesignable class MyLabel: UILabel {
#IBInspectable var style:String="p"{
didSet{self.font=MyStyle.fontForStyle(style)}
}
}
Add a UILabel to your view in Interface Builder and set its class to MyLabel. Then in the Attributes Inspector, you'll see a Style field where you can type h1, h2 or whatever, and the label's font will update right away. Want to change all your h1 labels to have a size of 48? Just update MyStyles and you'll see the changes in your XIBs/Storyboards right away.
This is going to be a huge time-saver, I hope some of you find it useful too!
I don't really get you mean. But you want to set custom "text style" from Interface Builder you can do this. for UILable
1, Create category UILabel + MyCustomStyle.
in UILabel + MyCustomStyle.h
#import <UIKit/UIKit.h>
#interface UILabel (MyCustomStyle)
#property (nonatomic, copy) NSString *mTextStyle;
#end
in UILabel + MyCustomStyle.m
#import "UILabel+MyCustomStyle.h"
#implementation UILabel (MyCustomStyle)
-(NSString *)mTextStyle {
return self.text;
}
-(void)setMTextStyle:(NSString *)txt{
if ([txt isEqualToString:#"h1"]) {
// custom style h1 code
self.font = ...;
}else if ([txt isEqualToString:#"h2"]){
// custom style h2 code
self.font = ...;
}
//... more custom style
}
2, Assign text style in IB

How to enable osf (old style figures) in ios?

So my problem is that i have font witch supports osf (old style figures) but i don't know how to enable it with UIFont?
I have figured it out.
OSF is optional feature included in some fonts, so we need to activate this feature using
UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:#{
UIFontDescriptorNameAttribute : #"MyriadPro-Light",
UIFontDescriptorFeatureSettingsAttribute : #[osfFontFeature]
}];
where osfFontFeature is
NSDictionary *osfFontFeature = #{
UIFontFeatureTypeIdentifierKey : #(21),
UIFontFeatureSelectorIdentifierKey : #(0)
};
Feature type and selector identifier keys can be obtained here:
https://developer.apple.com/fonts/TrueType-Reference-Manual/RM09/AppendixF.html#Type21
Finally, we create font with
[UIFont fontWithDescriptor:fontDescriptor size:17.0]

Attributed string with custom fonts in storyboard does not load correctly

We are using custom fonts in our project. It works well in Xcode 5. In Xcode 6, it works in plain text, attributed string in code. But those attributed strings set in storyboard all revert to Helvetica when running on simulator or device, although they look all right in storyboard.
I'm not sure if it's a bug of Xcode 6 or iOS 8 SDK, or the way to use custom fonts is changed in Xcode 6 / iOS 8?
The simplest answer that worked for is to drag the fonts into FontBook. If the fonts are in your project but not in your computer's FontBook, IB sometimes has trouble finding it. Weird, but has worked for me on several occasions.
The fix for me was to use an IBDesignable class:
import UIKit
#IBDesignable class TIFAttributedLabel: UILabel {
#IBInspectable var fontSize: CGFloat = 13.0
#IBInspectable var fontFamily: String = "DIN Light"
override func awakeFromNib() {
var attrString = NSMutableAttributedString(attributedString: self.attributedText)
attrString.addAttribute(NSFontAttributeName, value: UIFont(name: self.fontFamily, size: self.fontSize)!, range: NSMakeRange(0, attrString.length))
self.attributedText = attrString
}
}
Giving you this in the Interface Builder:
You can set up your attributedstring just as you normal do, but you'll have to set your fontsize and fontfamily once again in the new available properties.
As the Interface Builder is working with the custom font by default, this results in a what you see is what you get, which I prefer when building apps.
Note
The reason I'm using this instead of just the plain version is that I'm setting properties on the attributed label like the linespacing, which are not available when using the plain style.
You can add custom fonts to font book.
Step1: Click on manage fonts. It opens the font book.
Step2: Click on plus and add your fonts.
Next time when you click on font with attributed text newly added font also will show in the list. But make sure your custom font added in info.plist and bundle resources.
My solution is a bit of a work around. The real solution is for apple to fix Interface Builder.
With it you can mark all the bold and italic text in interface builder using a system font, then at runtime render your custom font. May not be optimal in all cases.
NSMutableAttributedString* ApplyCustomFont(NSAttributedString *attributedText,
UIFont* boldFont,
UIFont* italicFont,
UIFont* boldItalicFont,
UIFont* regularFont)
{
NSMutableAttributedString *attrib = [[NSMutableAttributedString alloc] initWithAttributedString:attributedText];
[attrib beginEditing];
[attrib enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attrib.length) options:0
usingBlock:^(id value, NSRange range, BOOL *stop)
{
if (value)
{
UIFont *oldFont = (UIFont *)value;
NSLog(#"%#",oldFont.fontName);
[attrib removeAttribute:NSFontAttributeName range:range];
if([oldFont.fontName rangeOfString:#"BoldItalic"].location != NSNotFound && boldItalicFont != nil)
[attrib addAttribute:NSFontAttributeName value:boldItalicFont range:range];
else if([oldFont.fontName rangeOfString:#"Italic"].location != NSNotFound && italicFont != nil)
[attrib addAttribute:NSFontAttributeName value:italicFont range:range];
else if([oldFont.fontName rangeOfString:#"Bold"].location != NSNotFound && boldFont != nil)
[attrib addAttribute:NSFontAttributeName value:boldFont range:range];
else if(regularFont != nil)
[attrib addAttribute:NSFontAttributeName value:regularFont range:range];
}
}];
[attrib endEditing];
return attrib;
}
Inspired by this post
Thanks to this thread, I've come to this solution:
private let fontMapping = [
"HelveticaNeue-Medium": "ITCAvantGardePro-Md",
"HelveticaNeue": "ITCAvantGardePro-Bk",
"HelveticaNeue-Bold": "ITCAvantGardePro-Demi",
]
func switchFontFamily(string: NSAttributedString) -> NSAttributedString {
var result = NSMutableAttributedString(attributedString: string)
string.enumerateAttribute(NSFontAttributeName, inRange: NSRange(location: 0, length: string.length), options: nil) { (font, range, _) in
if let font = font as? UIFont {
result.removeAttribute(NSFontAttributeName, range: range)
result.addAttribute(NSFontAttributeName, value: UIFont(name: fontMapping[font.fontName]!, size: font.pointSize)!, range: range)
}
}
return result
}
I have struggled with this bug: UILabel displays correctly in IB with custom font but does not display correctly on device or simulator (font is included in the project and is used in plain UILabels).
Finally found Attributed String Creator on (Mac) App Store. Generates code to be placed in your app in the appropriate place. Fantastic.
I am not the creator, just a happy user.
Met the same problem: the attribute font set for TextView in storyboard didn't work in run time with XCode 6.1 & iOS 8 SDK.
This is how I solved this issue, might be a workaround for you:
open the attribute inspector of your textview, change text to
"Plain"
click on the cross to delete the "wC hR"(red below)
change text to "Attributed", and then you can set the font and size
for your text.
run to check if it works
Try this it will work
In my case when i try to set "Silversky Technology" as Attributed text for label from interface builder its not show when i run in simulator but its show in interface builder. So i used one trick i made Silversky font with 1 pixel bigger then Technology text.
Attribute text have issue with same size of font so change size of 1 word this thing work with me.
May be this is xcode bug but this trick work for me.
Met the same problem: the attribute font for UILabel in storyboard didn't work in run time. Using this UIFont+IBCustomFonts.m works for me
https://github.com/deni2s/IBCustomFonts
The same problem.
Solved: Just check Selectable in TextView. Without this i have standard System font.
Double click and install the font to the system. It will work (Xcode 8.2)
#Hamidptb solution works, make sure to get the correct name of the font (once you've added it to Font Book)
Open the Font Book application, navigate to your font then press Command+I. The PostScript name is the font name you want to use here:
UILabel.appearance().font = UIFont(name: "PostScriptName", size: 17)
I was trying to get tableView cells with text having multiple paragraphs. The attributed strings seemed to be a way to get extra space between the paragraphs (something a bit nicer looking than doing two line-feeds in the string). Came across this and other posts when I discovered that the IB settings didn't apply at run time when you wanted to put different text in the cell.
The main thing I came up with was adding an extension to String (using Swift) to
create an attributed string with certain characteristics. Example here uses the Marker Felt font, as it is easily distinguishable from Helvetica. The example also shows a little extra bit of spacing between paragraphs to make them more distinct from each other.
extension String {
func toMarkerFelt() -> NSAttributedString {
var style = NSMutableParagraphStyle()
style.paragraphSpacing = 5.0
let markerFontAttributes : [NSObject : AnyObject]? = [
NSFontAttributeName : UIFont(name: "Marker Felt", size: 14.0)!,
NSParagraphStyleAttributeName: style,
NSForegroundColorAttributeName : UIColor.blackColor()
]
let s = NSAttributedString(string: self, attributes: markerFontAttributes)
return s
}
}
Then, in my custom tableViewCell, you send it the text you want and it converts it to an attributed string on the UILabel.
// MarkerFeltCell.swift
class MarkerFeltCell: UITableViewCell {
#IBOutlet weak var myLabel: UILabel!
func configureCellWithString(inputString : String) {
myLabel.attributedText = inputString.toMarkerFelt()
}}
In the view controller with the tableView, you should register your cell in viewDidLoad() -- I used a nib, so something like:
let cellName = "MarkerFeltCell"
tableView.registerNib(UINib(nibName: cellName, bundle: nil), forCellReuseIdentifier: cellName)
To get the cell to figure out how tall it should be, make a prototype cell that is used to get size info, and is never added into the tableView. So, in your
view controller's variables:
var prototypeSummaryCell : MarkerFeltCell? = nil
Then in (probably override - depending on your view controller) heightForRowAtIndexPath:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// ...
if xib == "MarkerFeltCell" {
if prototypeCell == nil {
prototypeCell = tableView.dequeueReusableCellWithIdentifier(xib) as? MarkerFeltCell
}
let width : CGFloat = tableView.bounds.width
let height : CGFloat = prototypeCell!.bounds.height
prototypeCell?.bounds = CGRect(x: 0, y: 0, width: width, height: height)
configureCell(prototypeCell!, atIndexPath: indexPath)
prototypeSummaryCell?.layoutIfNeeded()
let size = prototypeSummaryCell!.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
let nextHeight : CGFloat = ceil(size.height + 1.0)
return nextHeight
} else { // ...
In the above code, the prototypeCell will be filled in the first time it is needed. The prototypeCell is then used to figure out the height for the cell after going through the autosizing process. You will need to round up the height with the ceil() function. I also added in some extra fudge factor.
The final code bit is how you configure the text for the cell. For this example, simply:
func configureCell(cell :UITableViewCell, atIndexPath indexPath: NSIndexPath) {
if let realCell = cell as? MarkerFeltCell {
realCell.configureCellWithString("Multi-line string.\nLine 2.\nLine 3.") // Use \n to separate lines
}
}
Also, here is a shot of the nib. Pinned the label to the edges of the cell (with margin desired), but used a "Greater Than or Equal" constraint, with a less than "Required" priority for the bottom constraint.
Set the label's font to Attributed. Actual IB font didn't matter.
The result in this case:
In case of attributed string you can add custom font in font list as -
Click on font icon this will display following dialog .In the following dialog you can add your own category or existing one for custom font.attributed font dialog
After it click on Manage Fonts it open the following dialog select category in you created or existing one . Click on + sign to add font in the category.
Manage font dialog
that's have a simple and quick solition and that's work in my case .
that solution is add a code line in didFinishLaunchingWithOptions func in AppDelegate.swift file :
for textViews :
UITextView.appearance().font = UIFont(name: "IranSans", size: 17)
for labels :
UILabel.appearance().font = UIFont(name: "IranSans", size: 17)
and for rest of UiView like this two ☝️
For anyone applying custom fonts to attributed string in code: Try setting it in viewDidLayoutSubviews. My mistake was doing it in viewDidLoad, it won't be applied there.

Picking a random font from UIFont

How can I pick a random UIFont by name (with a constant size)?
There isn't much more to this question but it seems SO wants more from me so here is some meaningless twaddle.
create an array of all the fonts you want and then use arc4rand to pick a random object at index to use as your font.
Try this:
extension UIFont {
static var random: UIFont {
return UIFont(name: UIFont.familyNames.randomElement()!, size: CGFloat.random(in: 5...15))!
}
}
Then use this extension as usual:
let font = UIFont.random
This gives a fully random font with random size (you can of course eliminate this feature if it is not needed)

get font-weight of an uitextview

I want to resize the font-size in some UITextViews. That works fine with an outlet collection and this code:
for (UITextView *view in self.viewsToResize) {
if ([view respondsToSelector:#selector(setFont:)]) {
[view setFont:[UIFont systemFontOfSize:view.font.pointSize + 5]];
}
}
But my problem is, that not every textView uses the systemFont in normal weight, some of them are in bold weight. Is it possible to get the font-weight? With a po view.font in the debug area I can see everything I need:
$11 = 0x0c596ea0 <UICFFont: 0xc596ea0> font-family: "Helvetica"; font-weight: normal; font-style: normal; font-size: 12px
But how can I access the font-weight?
Using a second outlet collection for the bold views could solve my problem. But I'm wondering that I found nothing to get only the font-weight.
I have figured out how to get the font weights, you have to spelunk down to Core Text:
let ctFont = font as CTFont
let traits = CTFontCopyTraits(ctFont)
let dict = traits as Dictionary
if let weightNumber = dict[kCTFontWeightTrait] as? NSNumber {
print(weightNumber.doubleValue)
}
Enjoy!
UIFont does not have a bold/italic/... property, so you will have to rely on the font name only.
This will be a problem if you don't know which fonts will be used.
In the case you know that you will use eg. only Helvetica you can try this:
UIFont *font = textview.font;
if([font.fontName isEqualToString:#"Helvetica-Bold"])
NSLog(#"It's Bold!");
Alternatively you can search font.fontName for the word "bold"/"medium"/"light" etc., but that's not a guarantee you will get something from every available font:
if ([font.fontName rangeOfString:#"bold" options:NSCaseInsensitiveSearch].location == NSNotFound) {
NSLog(#"font is not bold");
} else {
NSLog(#"font is bold!");
}
// if font.fontName contains "medium"....
// if font.fontName contains "italic"....
Check http://iosfonts.com/ for the available font names.
But my problem is, that not every textView uses the systemFont in
normal weight, some of them are in bold weight.
If you want to use Bold System Font then you can simply use
[UIFont boldSystemFontOfSize:15.0];
However, I am still thinking of that special case in which you need to use font-weight.
Update :
There is nothing in the UIFont Class using which you can get font-weight directly. You can take a look at UIFont Class Reference.
Only thing that you can do is to get the font-name and try to find out the "bold" sub-string in the font name. If any match found that means font-weight of that specific font is "bold".
But, still this is not the most efficient method.
You can get a UIFontDescriptor for a font using the fontDescriptor method. Then you get the fontAttributes dictionary of the UIFontDescriptor, get the UIFontDescriptorTraitsAttribute from the dictionary which returns another dictionary, then read the UIFontWeightTrait from that dictionary. Not tested.
Now it's tested: Doesn't work. fontAttributes always returns a dictionary with two keys for font name and font size, and that's it. I suppose "this doesn't work" is also an answer when something should work according to the documentation...
You can try symbolicTraits, but that's not useful either: It returns "bold" only if the whole font family is bold.

Resources