How to change truncate characters in UILabel? - iphone

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

Related

iOS Charts - Display highest and lowest value for CandleStick charts

I'm trying to create a candlestick chart using Charts
As you guys can notice from my screenshot, the chart only shows the highest and lowest values instead of displaying the values for all the candles. Is there any way I can implement that with the Charts framework?
Thanks in advance.
If you want to display only highest and lowest values, you need to implement you own renderer inherited from CandleStickChartRenderer. In fact you just need to override one function drawValues(context: CGContext).
I have made some example which contain a hundred lines of code, but in fact my custom code contains about thirty lines.
class MyCandleStickChartRenderer: CandleStickChartRenderer {
private var _xBounds = XBounds() // Reusable XBounds object
private var minValue: Double
private var maxValue: Double
// New constructor
init (view: CandleStickChartView, minValue: Double, maxValue: Double) {
self.minValue = minValue
self.maxValue = maxValue
super.init(dataProvider: view, animator: view.chartAnimator, viewPortHandler: view.viewPortHandler)
}
// Override draw function
override func drawValues(context: CGContext)
{
guard
let dataProvider = dataProvider,
let candleData = dataProvider.candleData
else { return }
guard isDrawingValuesAllowed(dataProvider: dataProvider) else { return }
var dataSets = candleData.dataSets
let phaseY = animator.phaseY
var pt = CGPoint()
for i in 0 ..< dataSets.count
{
guard let dataSet = dataSets[i] as? IBarLineScatterCandleBubbleChartDataSet
else { continue }
let valueFont = dataSet.valueFont
let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)
let valueToPixelMatrix = trans.valueToPixelMatrix
_xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator)
let lineHeight = valueFont.lineHeight
let yOffset: CGFloat = lineHeight + 5.0
for j in stride(from: _xBounds.min, through: _xBounds.range + _xBounds.min, by: 1)
{
guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { break }
guard e.high == maxValue || e.low == minValue else { continue }
pt.x = CGFloat(e.x)
if e.high == maxValue {
pt.y = CGFloat(e.high * phaseY)
} else if e.low == minValue {
pt.y = CGFloat(e.low * phaseY)
}
pt = pt.applying(valueToPixelMatrix)
if (!viewPortHandler.isInBoundsRight(pt.x))
{
break
}
if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y))
{
continue
}
if dataSet.isDrawValuesEnabled
{
// In this part we draw min and max values
var textValue: String?
var align: NSTextAlignment = .center
if e.high == maxValue {
pt.y -= yOffset
textValue = "← " + String(maxValue)
align = .left
} else if e.low == minValue {
pt.y += yOffset / 5
textValue = String(minValue) + " →"
align = .right
}
if let textValue = textValue {
ChartUtils.drawText(
context: context,
text: textValue,
point: CGPoint(
x: pt.x,
y: pt.y ),
align: align,
attributes: [NSAttributedStringKey.font: valueFont, NSAttributedStringKey.foregroundColor: dataSet.valueTextColorAt(j)])
}
}
}
}
}
}
Do not forget use you custom renderer for you chart. ;)
myCandleStickChartView.renderer = MyCandleStickChartRenderer(view: myCandleStickChartView, minValue: 400, maxValue: 1450)

calculating UIWebView height with images gives wrong answer

I found how to calculate dynamic uiwebview height according to its content. My code is :
println("webViweDidFinish started")
var frame:CGRect = myWebView.frame
frame.size.height = 1
var fittingSize :CGSize = myWebView.sizeThatFits(CGSizeZero)
frame.size = fittingSize
myWebView.frame = frame
var contentsizeHeight = webView.scrollView.contentSize.height
var yCoordinateOfWebView = myWebView.frame.origin.y
var contentHeight: CGFloat = webView.scrollView.contentSize.height;
myScrollView.contentSize = CGSizeMake(self.view.frame.width, yCoordinateOfWebView + contentsizeHeight + labelAuthorOfArticle.frame.height) //
UIView.animateWithDuration(0.5, animations: {
self.heightConstraints.constant = fittingSize.height
self.myWebView.layoutIfNeeded()
self.myScrollContentView.layoutIfNeeded()
})
self.activityIndicator.stopAnimating()
If I use this approach:
var height:NSString! = webView.stringByEvaluatingJavaScriptFromString("document.height;")
var heightInFloat = height.floatValue
How to convert height to CGFloat? I can only convert it to float as you see.
The problem is that when webview has too many images the height is calculated wrong. Oppositely when there is no image height is calculated right. When I reload the function, the second time it calculates right (with images). Here is content of string that I am loading:
class func formatContentText(inout text: NSString)-> NSString{
text = text.stringByReplacingOccurrencesOfString("src=\"", withString: "src=\"http://dixinews.kz")
var deviceWidth = "300"
text = "<html><head><meta name = \"viewport\" content = \"user-scalable=no, width=" + deviceWidth + " , maximum-scale=1.0\"><style>img{max-width:100%%;height:auto !important;width:auto !important;};</style></head><body>" + text + "</body></html>"
return text
}
I used approach with javascript. Here is my code:
func webViewDidFinishLoad(webView: UIWebView) {
var frame:CGRect = myWebView.frame
// javascript
var height:NSString! = webView.stringByEvaluatingJavaScriptFromString("document.height;")
var heightInFloat = height.floatValue // convert to float
var heightINCGFloat = CGFloat(heightInFloat) convert to cgfloat
frame.size.height = heightINCGFloat //set heigh
myWebView.frame = frame // set frame
myWebView.scrollView.contentSize.height = myWebView.frame.height
var contentsizeHeight = webView.scrollView.contentSize.height
var yCoordinateOfWebView = myWebView.frame.origin.y
myScrollView.contentSize = CGSizeMake(self.view.frame.width, yCoordinateOfWebView + contentsizeHeight + labelAuthorOfArticle.frame.height) //
UIView.animateWithDuration(0.5, animations: {
self.heightConstraints.constant = heightINCGFloat
self.myWebView.layoutIfNeeded()
self.myScrollContentView.layoutIfNeeded()
})
self.activityIndicator.stopAnimating()
}

create an equal space between labels in the scrollview with swift

I am creating a menu of the same style as the kiosk play Application
I would like the same space between labels, for I dynamically creates, I try SEVERAL way but it does not work.
I size of their label and I added the padding but it does work.
Here is the result
here is my code
func menu (value: [String]){
var pos: CGFloat = 50.0
var index: Int = 0
for index = 0; index < value.count ; index++ {
self.titleLabel = UILabel()
self.titleLabel.text = "\(value[index])"
self.titleLabel.textColor = UIColor.blackColor()
self.titleLabel.backgroundColor = UIColor.blueColor()
self.titleLabel.font = UIFont(name: "MarkerFelt-Wide", size: 20)
self.titleLabel.sizeToFit()
//width of the label
var widhtLabel = self.titleLabel.frame.size.width
// add padding
self.titleLabel.frame.origin.x = widhtLabel + pos
self.scrollViewMenu.addSubview(self.titleLabel)
pos += 150.0
println()
}
self.scrollViewMenu.contentSize = CGSize(width: self.titleLabel.frame.origin.x + 220, height:0)
}
thanks

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.

Resize font size in UITextView

Is there a way to shrink the font-size in a UITextView if there is too much text? Similar to the UILabel?
The problem with the accepted answer is that you have to guess the number of characters (the string's length) needed to fill the field, and that differs from font to font. Something like this, a category on UITextView, should work.
#import "UITextView+Size.h"
#define kMaxFieldHeight 1000
#implementation UITextView (Size)
-(BOOL)sizeFontToFitMinSize:(float)aMinFontSize maxSize:(float)aMaxFontSize {
float fudgeFactor = 16.0;
float fontSize = aMaxFontSize;
self.font = [self.font fontWithSize:fontSize];
CGSize tallerSize = CGSizeMake(self.frame.size.width-fudgeFactor,kMaxFieldHeight);
CGSize stringSize = [self.text sizeWithFont:self.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
while (stringSize.height >= self.frame.size.height) {
if (fontSize <= aMinFontSize) // it just won't fit, ever
return NO;
fontSize -= 1.0;
self.font = [self.font fontWithSize:fontSize];
tallerSize = CGSizeMake(self.frame.size.width-fudgeFactor,kMaxFieldHeight);
stringSize = [self.text sizeWithFont:self.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
}
return YES;
}
#end
Try this:
NSInteger lengthThreshold = 200;
if( [ textView.text length ] > lengthThreshold ) {
NSInteger newSize = ... //calculate new size based on length
[ textView setFont: [ UIFont systemFontOfSize: newSize ]];
}
Swift 4 implementation inspired by #Jane Sales's answer.
When calculating available width and height we must also take into consideration possible vertical and horizontal margins (textContainerInset and textContainer.lineFragmentPadding).
Here's a better explanation of how margins work on UITextView: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextUILayer/Tasks/SetTextMargins.html
If the text view can resize, then we must also force a layout so we can calculate the font size based on biggest possible text view size. In this case only height is considered (layouts only if required text height is bigger than original available height).
import UIKit
extension UITextView {
func adjustFontToFitText(minimumScale: CGFloat) {
guard let font = font else {
return
}
let scale = max(0.0, min(1.0, minimumScale))
let minimumFontSize = font.pointSize * scale
adjustFontToFitText(minimumFontSize: minimumFontSize)
}
func adjustFontToFitText(minimumFontSize: CGFloat) {
guard let font = font, minimumFontSize > 0.0 else {
return
}
let minimumSize = floor(minimumFontSize)
var fontSize = font.pointSize
let availableWidth = bounds.width - (textContainerInset.left + textContainerInset.right) - (2 * textContainer.lineFragmentPadding)
var availableHeight = bounds.height - (textContainerInset.top + textContainerInset.bottom)
let boundingSize = CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)
var height = text.boundingRect(with: boundingSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil).height
if height > availableHeight {
// If text view can vertically resize than we want to get the maximum possible height
sizeToFit()
layoutIfNeeded()
availableHeight = bounds.height - (textContainerInset.top + textContainerInset.bottom)
}
while height >= availableHeight {
guard fontSize > minimumSize else {
break
}
fontSize -= 1.0
let newFont = font.withSize(fontSize)
height = text.boundingRect(with: boundingSize, options: .usesLineFragmentOrigin, attributes: [.font: newFont], context: nil).height
}
self.font = font.withSize(fontSize)
}
}