CGPathRef from string - iphone

Please how can we get path of particular arabic of french letter ? I've just found out that CTFontCreatePathForGlyph will give CGPathRef like, but its will be the outline of text .
I need this real text path for showing a text drawing animation..
any help please

You dont require ur path to be converted into NSString at all.
You can create the path for text as follows:
CTFontRef font = CTFontCreateWithName(CFSTR("Helvetica-Bold"), 72.0f, NULL);
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
(id)font, kCTFontAttributeName,
nil];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:#"Hello World!"
attributes:attrs];
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attrString);
CFArrayRef runArray = CTLineGetGlyphRuns(line);
// for each RUN
for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runArray); runIndex++)
{
// Get FONT for this run
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);
CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
// for each GLYPH in run
for (CFIndex runGlyphIndex = 0; runGlyphIndex < CTRunGetGlyphCount(run); runGlyphIndex++)
{
// get Glyph & Glyph-data
CFRange thisGlyphRange = CFRangeMake(runGlyphIndex, 1);
CGGlyph glyph;
CGPoint position;
CTRunGetGlyphs(run, thisGlyphRange, &glyph);
CTRunGetPositions(run, thisGlyphRange, &position);
// Get PATH of outline
{
CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL);
CGAffineTransform t = CGAffineTransformMakeTranslation(position.x, position.y);
CGPathAddPath(letters, &t, letter);
CGPathRelease(letter);
}
}
}
CFRelease(line);
This is how you create a path, for sample code please refer this link. This code is a part of this sample project.
Hope this helps you

I needed this in Swift but it was painful to work out. I hope this is useful to someone else! I've written it as extensions to String and NSAttributedString to be more versatile.
You might see the letters as upside-down depending on how you're drawing your path. You can fix this by adding a vertical flip transform to the call to CTFontCreatePathForGlyph() (a vertical flip is just a CGAffineTransform with a scaleY of -1).
public extension String {
func path(withFont font: UIFont) -> CGPath {
let attributedString = NSAttributedString(string: self, attributes: [.font: font])
let path = attributedString.path()
return path
}
}
public extension NSAttributedString {
func path() -> CGPath {
let path = CGMutablePath()
// Use CoreText to lay the string out as a line
let line = CTLineCreateWithAttributedString(self as CFAttributedString)
// Iterate the runs on the line
let runArray = CTLineGetGlyphRuns(line)
let numRuns = CFArrayGetCount(runArray)
for runIndex in 0..<numRuns {
// Get the font for this run
let run = unsafeBitCast(CFArrayGetValueAtIndex(runArray, runIndex), to: CTRun.self)
let runAttributes = CTRunGetAttributes(run) as Dictionary
let runFont = runAttributes[kCTFontAttributeName] as! CTFont
// Iterate the glyphs in this run
let numGlyphs = CTRunGetGlyphCount(run)
for glyphIndex in 0..<numGlyphs {
let glyphRange = CFRangeMake(glyphIndex, 1)
// Get the glyph
var glyph : CGGlyph = 0
withUnsafeMutablePointer(to: &glyph) { glyphPtr in
CTRunGetGlyphs(run, glyphRange, glyphPtr)
}
// Get the position
var position : CGPoint = .zero
withUnsafeMutablePointer(to: &position) {positionPtr in
CTRunGetPositions(run, glyphRange, positionPtr)
}
// Get a path for the glyph
guard let glyphPath = CTFontCreatePathForGlyph(runFont, glyph, nil) else {
continue
}
// Transform the glyph as it is added to the final path
let t = CGAffineTransform(translationX: position.x, y: position.y)
path.addPath(glyphPath, transform: t)
}
}
return path
}
}

Related

draw text on CAShapelayer

I am drawing a line using coregraphics and I want to draw some text using core graphics.
My code is as follows;
import UIKit
class ArrowLineLayer: CAShapeLayer {
var startingGlyph : CGRect!
override init() {
super.init()
let fontName = "HelveticaNeue-Bold"
let font = UIFont(name:fontName , size: 15)
let measurementString = "28 cm" as NSString
measurementString.draw(at: CGPoint(x:150,y:100), withAttributes: [NSAttributedStringKey.font: font!])
}
func drawArrow(frame:CGRect)
{
self.frame = frame
self.lineWidth = 3
self.strokeColor = UIColor.red.cgColor
self.lineCap = "round"
let path = CGMutablePath()
path.move(to: CGPoint(x:100 , y:100))
path.addLine(to: CGPoint(x:200 , y:100))
let radius: CGFloat = 5.0
let rect = CGRect(x:100 - radius, y: 100 - radius , width: 2 * radius, height: 2 * radius)
startingGlyph = rect
path.addPath(UIBezierPath(ovalIn: rect).cgPath)
self.path = path
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
and then later I initialise this layer and add it to main view layer. I can see the line but text is not visible.I don't want to use the CATextlayer because I want to keep the whole logic at one place. Can anyone please point me what I am missing?
Regards,
neena
You can create UIBezierPath with the NSAttributedString and then append this path to your exist path.
extension UIBezierPath {
convenience init(text: NSAttributedString) {
let textPath = CGMutablePath()
let line = CTLineCreateWithAttributedString(text)
let runs = CTLineGetGlyphRuns(line) as! [CTRun]
for run in runs
{
let attributes: NSDictionary = CTRunGetAttributes(run)
let font = attributes[kCTFontAttributeName as String] as! CTFont
let count = CTRunGetGlyphCount(run)
for index in 0 ..< count
{
let range = CFRangeMake(index, 1)
var glyph = CGGlyph()
CTRunGetGlyphs(run, range, &glyph)
var position = CGPoint()
CTRunGetPositions(run, range, &position)
let letterPath = CTFontCreatePathForGlyph(font, glyph, nil)
let transform = CGAffineTransform(translationX: position.x, y: position.y)
textPath.addPath(letterPath!, transform: transform)
}
}
self.init(cgPath: textPath)
}
}

Swift converted to PDF but not stops

I want to generate pdf from textview after user enters the data. The textview contains paragraphs and attributed texts.
I have Followed
this procedure
and my code is:
func createPdf() {
createPDFNamed("test")
}
func getPDFPath(_ name: String) -> String {
let newPDFName = "\(name).pdf"
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
let pdfPath: String = (documentsDirectory as String).appending(newPDFName);
print(pdfPath)
return pdfPath
}
func createPDFNamed(_ name: String) {
let text = myTextView.text
let currentText: CFAttributedString = CFAttributedStringCreate(nil, (text as CFString?), nil)
if currentText != nil {
let framesetter: CTFramesetter = CTFramesetterCreateWithAttributedString(currentText)
if framesetter != nil {
let pdfFileName: String = getPDFPath(name)
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRect.zero, nil)
var currentRange: CFRange! = CFRangeMake(0, 0)
var currentPage: Int = 0
var done = false
repeat {
UIGraphicsBeginPDFPageWithInfo(CGRect(x: 0, y: 0, width: 612, height: 792), nil)
currentPage += 1
drawPageNbr(currentPage)
currentRange = renderPagewithTextRange(currentRange, andFramesetter: framesetter)
if currentRange.location == CFAttributedStringGetLength((currentText as? CFAttributedString)) {
done = true
}
} while !done
UIGraphicsEndPDFContext()
}
else {
print("Could not create the framesetter needed to lay out the atrributed string.")
}
}
else {
print("Could not create the attributed string for the framesetter")
}
}
func renderPagewithTextRange(_ currentRange: CFRange, andFramesetter framesetter: CTFramesetter) -> CFRange {
var currentRange: CFRange! = CFRangeMake(0, 0)
let currentContext: CGContext? = UIGraphicsGetCurrentContext()
currentContext?.textMatrix = .identity
let frameRect = CGRect(x: 72, y: 72, width: 468, height: 648)
let framePath: CGMutablePath = CGMutablePath()
framePath.addRect(frameRect, transform: .identity)
let frameRef: CTFrame = CTFramesetterCreateFrame(framesetter, currentRange, framePath, nil)
currentContext?.translateBy(x: 0, y: 792)
currentContext?.scaleBy(x: 1.0, y: -1.0)
CTFrameDraw(frameRef, currentContext!)
currentRange = CTFrameGetVisibleStringRange(frameRef)
currentRange.location += currentRange.length
currentRange.length = 0 as? CFIndex ?? CFIndex()
return currentRange
}
func drawPageNumber(_ pageNum: Int) {
let pageString = "Page \(Int(pageNum))"
let theFont = UIFont.systemFont(ofSize: 12)
let pageStringSize: CGSize = pageString.size(withAttributes: [.font: UIFont.systemFont(ofSize: 17.0)])
let stringRect = CGRect(x: ((612.0 - pageStringSize.width) / 2.0), y: 720.0 + ((72.0 - pageStringSize.height) / 2.0), width: pageStringSize.width, height: pageStringSize.height)
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle
paragraphStyle?.lineBreakMode = .byTruncatingTail
paragraphStyle?.alignment = .right
let attributes = [NSAttributedStringKey.font: theFont, .paragraphStyle: paragraphStyle as Any] as [NSAttributedStringKey : Any]
pageString.draw(in: stringRect, withAttributes: attributes)
}
The pdf generates and never ends even it went upto 2Gb file size. But not opening. Could you please advice where I am doing mistake to finish the pdf generation? Also after pdf creation, would like to send by mail
Thanks in advance
EDIT:
If I change the code to while done it stops but prints one page only. If I change to while !done it goes on generating pdf. Past two days searching and not end up with any solution. Also the pdf prints black only. I want to reflect the attributes to pdf. Any suggestions.
if (currentRange.location == CFAttributedStringGetLength(currentText)){
done = true
}
} while done
UIGraphicsEndPDFContext()
}
else {
print("Could not create the framesetter needed to lay out the atrributed string.")
}
}
else {
print("Could not create the attributed string for the framesetter")
}
}

How to draw vertical text in UILabel

I'm currently working on drawing vertical Chinese text in a label. Here's what I am trying to achieve, albeit with Chinese Characters:
I've been planning to draw each character, rotate each character 90 degrees to the left, then rotating the entire label via affine transformations to get the final result. However, it feels awfully complicated. Is there an easier way to draw the text without complicated CoreGraphics magic that I'm missing?
Well, You can do like below:
labelObject.numberOfLines = 0;
labelObject.lineBreakMode = NSLineBreakByCharWrapping;
and setFrame with -- height:100, width:20 It will work fine..
It works
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 30, 100)];
lbl.transform = CGAffineTransformMakeRotation((M_PI)/2);
Tried the method offered by Simha.IC but it didn't work well for me. Some characters are thinner than others and get placed two on a line. E.g.
W
ai
ti
n
g
The solution for me was to create a method that transforms the string itself into a multiline text by adding \n after each character. Here's the method:
- (NSString *)transformStringToVertical:(NSString *)originalString
{
NSMutableString *mutableString = [NSMutableString stringWithString:originalString];
NSRange stringRange = [mutableString rangeOfString:mutableString];
for (int i = 1; i < stringRange.length*2 - 2; i+=2)
{
[mutableString insertString:#"\n" atIndex:i];
}
return mutableString;
}
Then you just setup the label like this:
label.text = [self transformStringToVertical:myString];
CGRect labelFrame = label.frame;
labelFrame.size.width = label.font.pointSize;
labelFrame.size.height = label.font.lineHeight * myString.length;
label.frame = labelFrame;
Enjoy!
If you would like to rotate the whole label (including characters), you can do so as follows:
First add the QuartzCore library to your project.
Create a label:
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 30.0)];
[label setText:#"Label Text"];
Rotate the label:
[label setTransform:CGAffineTransformMakeRotation(-M_PI / 2)];
Depending on how you'd like to position the label you may need to set the anchor point. This sets the point around which a rotation occurs. Eg:
[label.layer setAnchorPoint:CGPointMake(0.0, 1.0)];
This is another way to draw vertical text, by subclassing UILabel. But it is some kind different of what the question want.
Objective-C
#implementation MyVerticalLabel
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformMakeRotation(-M_PI_2);
CGContextConcatCTM(context, transform);
CGContextTranslateCTM(context, -rect.size.height, 0);
CGRect newRect = CGRectApplyAffineTransform(rect, transform);
newRect.origin = CGPointZero;
NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = self.lineBreakMode;
textStyle.alignment = self.textAlignment;
NSDictionary *attributeDict =
#{
NSFontAttributeName : self.font,
NSForegroundColorAttributeName : self.textColor,
NSParagraphStyleAttributeName : textStyle,
};
[self.text drawInRect:newRect withAttributes:attributeDict];
}
#end
A sample image is following:
Swift
It can put on the storyboard, and watch the result directly. Like the image, it's frame will contain the vertical text. And text attributes, like textAlignment, font, work well too.
#IBDesignable
class MyVerticalLabel: UILabel {
override func drawRect(rect: CGRect) {
guard let text = self.text else {
return
}
// Drawing code
let context = UIGraphicsGetCurrentContext()
let transform = CGAffineTransformMakeRotation( CGFloat(-M_PI_2))
CGContextConcatCTM(context, transform)
CGContextTranslateCTM(context, -rect.size.height, 0)
var newRect = CGRectApplyAffineTransform(rect, transform)
newRect.origin = CGPointZero
let textStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
textStyle.lineBreakMode = self.lineBreakMode
textStyle.alignment = self.textAlignment
let attributeDict: [String:AnyObject] = [
NSFontAttributeName: self.font,
NSForegroundColorAttributeName: self.textColor,
NSParagraphStyleAttributeName: textStyle,
]
let nsStr = text as NSString
nsStr.drawInRect(newRect, withAttributes: attributeDict)
}
}
Swift 4
override func draw(_ rect: CGRect) {
guard let text = self.text else {
return
}
// Drawing code
if let context = UIGraphicsGetCurrentContext() {
let transform = CGAffineTransform( rotationAngle: CGFloat(-Double.pi/2))
context.concatenate(transform)
context.translateBy(x: -rect.size.height, y: 0)
var newRect = rect.applying(transform)
newRect.origin = CGPoint.zero
let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
textStyle.lineBreakMode = self.lineBreakMode
textStyle.alignment = self.textAlignment
let attributeDict: [NSAttributedStringKey: AnyObject] = [NSAttributedStringKey.font: self.font, NSAttributedStringKey.foregroundColor: self.textColor, NSAttributedStringKey.paragraphStyle: textStyle]
let nsStr = text as NSString
nsStr.draw(in: newRect, withAttributes: attributeDict)
}
}
Swift 5
More easy way with CGAffineTransform
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var verticalText: UILabel
override func viewDidLoad() {
verticalText.transform = CGAffineTransform(rotationAngle:CGFloat.pi/2)
}
}
import UIKit
class VerticalLabel : UILabel {
private var _text : String? = nil
override var text : String? {
get {
return _text
}
set {
self.numberOfLines = 0
self.textAlignment = .center
self.lineBreakMode = .byWordWrapping
_text = newValue
if let t = _text {
var s = ""
for c in t {
s += "\(c)\n"
}
super.text = s
}
}
}
}

How to change truncate characters in UILabel?

When the text of a UILabel gets truncated there are 3 dots inserted by default.
Is it possible to change these characters or disable them?
I have written a custom truncating class that you can pop into you code where ever. Just use this method below. it will return true if truncation has taken place, and MaxWidth can be left as 0 if you just want to use the labels default frame width. Put maxWidth as something less than the frames width to shorten it within its frames bounds.
Swift 2 (with some swift 3 comments for converting)
usage:
Truncater.replaceElipsis(forLabel: label, withString: "???")
let didTruncate = Truncater.replaceElipsis(forLabel: label, withString: "1234", andMaximumWidth: 50) //maxWidth is not number of chars, but label width in CGFloat
class:
import UIKit
class Truncater {
class func replaceElipsis(forLabel label:UILabel, withString replacement:String) -> Bool {
return replaceElipsis(forLabel: label, withString: replacement, andMaximumWidth:0)
}
class func replaceElipsis(forLabel label:UILabel, withString replacement:String, andMaximumWidth width:CGFloat) -> Bool {
if(label.text == nil){
return false
}
let origSize = label.frame;
var useWidth = width
if(width <= 0){
useWidth = origSize.width //use label width by default if width <= 0
}
label.sizeToFit()
let labelSize = label.text!.sizeWithAttributes([NSFontAttributeName: label.font]) //.size(attributes: [NSFontAttributeName: label.font]) for swift 3
if(labelSize.width > useWidth){
let original = label.text!;
let truncateWidth = useWidth;
let font = label.font;
let subLength = label.text!.characters.count
var temp = label.text!.substringToIndex(label.text!.endIndex.advancedBy(-1)) //label.text!.substring(to: label.text!.index(label.text!.endIndex, offsetBy: -1)) for swift 3
temp = temp.substringToIndex(temp.startIndex.advancedBy(getTruncatedStringPoint(subLength, original:original, truncatedWidth:truncateWidth, font:font, length:subLength)))
temp = String.localizedStringWithFormat("%#%#", temp, replacement)
var count = 0
while temp.sizeWithAttributes([NSFontAttributeName: label.font]).width > useWidth {
count+=1
temp = label.text!.substringToIndex(label.text!.endIndex.advancedBy(-(1+count)))
temp = temp.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) //remove this if you want to keep whitespace on the end
temp = String.localizedStringWithFormat("%#%#", temp, replacement)
}
label.text = temp;
label.frame = origSize;
return true;
}
else {
label.frame = origSize;
return false
}
}
class func getTruncatedStringPoint(splitPoint:Int, original:String, truncatedWidth:CGFloat, font:UIFont, length:Int) -> Int {
let splitLeft = original.substringToIndex(original.startIndex.advancedBy(splitPoint))
let subLength = length/2
if(subLength <= 0){
return splitPoint
}
let width = splitLeft.sizeWithAttributes([NSFontAttributeName: font]).width
if(width > truncatedWidth) {
return getTruncatedStringPoint(splitPoint - subLength, original: original, truncatedWidth: truncatedWidth, font: font, length: subLength)
}
else if (width < truncatedWidth) {
return getTruncatedStringPoint(splitPoint + subLength, original: original, truncatedWidth: truncatedWidth, font: font, length: subLength)
}
else {
return splitPoint
}
}
}
Objective C
+ (bool) replaceElipsesForLabel:(UILabel*) label With:(NSString*) replacement MaxWidth:(float) width
class:
//=============================================Header=====================================================
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface CustomTruncater : NSObject
+ (bool) replaceElipsesForLabel:(UILabel*) label With:(NSString*) replacement MaxWidth:(float) width;
#end
//========================================================================================================
#import "CustomTruncater.h"
#implementation CustomTruncater
static NSString *original;
static float truncateWidth;
static UIFont *font;
static int subLength;
+ (bool) replaceElipsesForLabel:(UILabel*) label With:(NSString*) replacement MaxWidth:(float) width {
CGRect origSize = label.frame;
float useWidth = width;
if(width <= 0)
useWidth = origSize.size.width; //use label width by default if width <= 0
[label sizeToFit];
CGSize labelSize = [label.text sizeWithFont:label.font];
if(labelSize.width > useWidth) {
original = label.text;
truncateWidth = useWidth;
font = label.font;
subLength = label.text.length;
NSString *temp = [label.text substringToIndex:label.text.length-1];
temp = [temp substringToIndex:[self getTruncatedStringPoint:subLength]];
temp = [NSString stringWithFormat:#"%#%#", temp, replacement];
int count = 0;
while([temp sizeWithFont:label.font].width > useWidth){
count++;
temp = [label.text substringToIndex:(label.text.length-(1+count))];
temp = [temp stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; //remove this if you want to keep whitespace on the end
temp = [NSString stringWithFormat:#"%#%#", temp, replacement];
}
label.text = temp;
label.frame = origSize;
return true;
}
else {
label.frame = origSize;
return false;
}
}
+ (int) getTruncatedStringPoint:(int) splitPoint {
NSString *splitLeft = [original substringToIndex:splitPoint];
subLength /= 2;
if(subLength <= 0)
return splitPoint;
if([splitLeft sizeWithFont:font].width > truncateWidth){
return [self getTruncatedStringPoint:(splitPoint - subLength)];
}
else if ([splitLeft sizeWithFont:font].width < truncateWidth) {
return [self getTruncatedStringPoint:(splitPoint + subLength)];
}
else {
return splitPoint;
}
}
#end
Look at -[UILabel setLineBreakMode:] and UILineBreakModeCharacterWrap. The default value of -[UILabel lineBreakMode] is UILineBreakModeTailTruncation, which causes the ellipsis at the end.
As Javanator said you would have to do your own truncation. You shuld use the
sizeWithFont:forWidth:lineBreakMode: message on the UIKit additions to NSString class to get the width of a string with a certain font. This will handle all types of fonts.
Link
You can also set
[lbl setAdjustsFontSizeToFitWidth:YES];
With this there will be no need of truncating text and you can display the complete text on your label.
I would like to provide a more Swifty version of what Fonix provided earlier and using Swift 5 syntax. Also I decided to write the functions as an extension of UILabel.
extension UILabel {
func replaceEllipsis(withString replacement: String, andMaximumWidth width: CGFloat = 0) -> Bool {
if let labelText = self.text, let font = self.font {
let origSize = self.frame
var useWidth = width
if width <= 0 {
useWidth = origSize.width // use label width by default if width <= 0
}
self.sizeToFit()
let labelSize = labelText.size(withAttributes: [NSAttributedString.Key.font: font])
if labelSize.width > useWidth {
let truncateWidth = useWidth
let subLength = labelText.count
var newText = String(labelText[..<labelText.index(labelText.endIndex, offsetBy: -1)])
newText = String(newText[..<newText.index(labelText.startIndex, offsetBy: getTruncatedStringPoint(splitPoint: subLength,
original: labelText,
truncatedWidth: truncateWidth,
font: font,
length: subLength))])
newText = String.localizedStringWithFormat("%#%#", newText, replacement)
var count = 0
while newText.size(withAttributes: [NSAttributedString.Key.font: font]).width > useWidth {
count += 1
newText = String(labelText[..<labelText.index(labelText.endIndex, offsetBy: -(1 + count))])
newText = newText.trimmingCharacters(in: NSCharacterSet.whitespaces)
newText = String.localizedStringWithFormat("%#%#", newText, replacement)
}
self.text = newText
self.frame = origSize
return true
} else {
self.frame = origSize
return false
}
} else {
return false
}
}
private func getTruncatedStringPoint(splitPoint: Int, original: String, truncatedWidth: CGFloat, font: UIFont, length: Int) -> Int {
let index = original.index(original.startIndex, offsetBy: splitPoint)
let splitLeft = String(original[..<index])
let subLength = length / 2
if subLength <= 0 {
return splitPoint
}
let width = splitLeft.size(withAttributes: [NSAttributedString.Key.font: font]).width
if width > truncatedWidth {
return getTruncatedStringPoint(splitPoint: splitPoint - subLength, original: original, truncatedWidth: truncatedWidth, font: font, length: subLength)
} else if width < truncatedWidth {
return getTruncatedStringPoint(splitPoint: splitPoint + subLength, original: original, truncatedWidth: truncatedWidth, font: font, length: subLength)
} else {
return splitPoint
}
}
}
It'll be used as follows:
<UILabel>.replaceEllipsis(withString: " ...Read More") // if you want to use the label width
Also you can pass a custom width as well if you need to. I opted for the default width in the above example.
For references on what I used in my refactor, the below StackOverflow links were helpful:
Advanced by refactor
substringToIndex refactor
why dont you code to count the length of string and makes its substring if its exceeding the view. or do anything you want
It is raw but effective method

finding location of specific characters in UILabel on iPhone

I have a UILabel with some text, say "Hello World abcdefg" The label can have multiple lines, different font sizes etc.
Question: How do I find the coordinates of all letters "d" in this UILabel.
Logical first step is find the position of those characters in the string (UILabel.text), but then how do I translate that into coordinates when it's actually drawn on screen
The idea is to find those coordinates and draw something custom on top of that character (basically to cover it with a custom image)
The basic tools for measuring text on iPhone are in UIStringDrawing.h but none of them do what you need. You will basically have to iterate through substrings one character at a time measuring each. When a line wraps (the result is taller), split after the last character that did not wrap and add the line height to your y coordinate.
- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode;
Methods have changed since iOS 7.0 came out. Try this
- (CGFloat)charactersOffsetBeforeDayPartOfLabel {
NSRange range = [[self stringFromDate:self.currentDate] rangeOfString:[NSString stringWithFormat:#"%i",[self dayFromDate:self.currentDate]]];
NSString *chars = [[self stringFromDate:self.currentDate] substringToIndex:range.location];
NSMutableArray *arrayOfChars = [[NSMutableArray alloc]init];
[chars enumerateSubstringsInRange:NSMakeRange(0, [chars length]) options:(NSStringEnumerationByComposedCharacterSequences) usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[arrayOfChars addObject:substring];
}];
CGFloat charsOffsetTotal = 0;
for (NSString *i in arrayOfChars){
NSDictionary *attributes = #{NSFontAttributeName: [UIFont fontWithName:#"Helvetica Neue" size:16.0f]};
charsOffsetTotal += [i sizeWithAttributes:attributes].width;
}
return charsOffsetTotal;
}
Here ya go:
fileprivate let selfSizing = UILabel()
class DualColorLabel: UILabel
{
var filled: UIColor?
var unfilled: UIColor?
var origin: String?
var widths: [CGFloat] = []
var fuckupLockup = false
override var text: String? {
didSet {
if fuckupLockup {
print ("SDBOFLAG-13822 wtf?")
}
}
}
func setupColorsAndText(filled: UIColor,
unfilled: UIColor)
{
self.filled = filled
self.unfilled = unfilled
guard let text = origin, text.count > 0 else {
assertionFailure("usage error")
return
}
guard font != nil else {
usageError()
return
}
for index in 1...text.count {
let s = String.Index(utf16Offset: 0, in: text)
let e = String.Index(utf16Offset: index, in: text)
let beginning = text[s..<e]
let p = String(beginning)
selfSizing.font = font
selfSizing.text = p
let size = selfSizing.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
let width = size.width
widths.append(width)
}
}
func setupfill(adjusted: CGRect)
{
assert(adjusted.origin.x <= 0, "fixed this code for fill in the middle: currently supported only fill at start")
let endOffset = adjusted.width + adjusted.origin.x
guard let font = self.font else {
usageError()
return
}
guard let origin = origin, let filled = filled,
let unfilled = unfilled else {
usageError()
return
}
var idx = String.Index(utf16Offset: origin.count, in: origin)
for (index, width) in widths.enumerated() {
if endOffset < width {
idx = String.Index(utf16Offset: index, in: origin)
print ("SDBOFLAG-13822 index \(index) for text \(origin)")
break
}
}
let total = NSMutableAttributedString()
do {
let s = String.Index(utf16Offset: 0, in: origin)
let beginning = origin[s..<idx]
let p = String(beginning)
print("SDBOFLAG-13822 filled text \(p)")
let filledAttributes:
[NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor:
// UIColor.yellow,
filled,
NSAttributedString.Key.font:
font
]
let filledPortion = NSAttributedString(string: p, attributes: filledAttributes)
total.append(filledPortion)
}
let unfilledAttributes:
[NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor:
// UIColor.blue,
unfilled,
NSAttributedString.Key.font: font]
let e = String.Index(utf16Offset: origin.count, in: origin)
let ending = origin[idx..<e]
let str = String(ending)
print("SDBOFLAG-13822 unfilled text \(str)")
let unfilledPortion = NSAttributedString(string: str, attributes: unfilledAttributes)
total.append(unfilledPortion)
self.attributedText = total
fuckupLockup = true
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
func usageError()
{
assertionFailure("usage error")
}
The width calculation for fragments goes into widths array per suggestions provided.