swift: edit text in JSON - ios

I have JSON file with text and label. In JSON file I want to add this characters <b> or <i> to some words. For example:
"this is my text <b>bold<b> or <i>italic<i>"
And before when I show text in label I want to analyse my text and if some words have this characters <b> or <i> I want to make this word bold or italic in the text of the label. How to do it? Or there are another way to make bold text from JSON?
UPDATE:
Yours code:
/* Set Tagged Text & Fonts */
let taggedTextString = myText.text
let tagFont = UIFont.boldSystemFont(ofSize: 17.0)
let normalFont = UIFont.systemFont(ofSize: 17.0)
/* You can simply assign the AttributedText to the modified String, which may return nil. */
textLabel.attributedText = taggedTextString.modifyFontWithTags(openingTag: "<tag>", closingTag: "</tag>", taggedFont: tagFont, unTaggedFont: normalFont)
My code:
var attributedString = NSMutableAttributedString(string: myText.text)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 5
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
textLabel.attributedText = attributedString;
Is this code the best way to union your and my code?
for i in stride(from: 0, to: allTagsSplitArray.count, by: 1) {
finalAttrStr.append(NSMutableAttributedString(string: allTagsSplitArray[i], attributes: [kCTFontAttributeName as NSAttributedStringKey : i % 2 == 0 ? untaggedTextFont : taggedTextFont]))
// my code
finalAttrStr.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, finalAttrStr.length))
}

The simplest way I seem to have accomplished this is by running through the string and scanning for your key tags - <b>, <i>, etc.
To make things easier (and more reusable), we should make an extension of our String or NSAttributedString classes. For this example, I've extended the String class, and and I return a NSAttributedString:
func modifyFontWithTags(openingTag: String, closingTag: String, taggedFont: UIFont?, unTaggedFont: UIFont?) -> NSMutableAttributedString? {
/* Make sure we have everything we need. */
guard let taggedTextFont = taggedFont,
let untaggedTextFont = unTaggedFont,
!self.isEmpty,
self.contains(openingTag),
self.contains(closingTag) else { return nil }
/* Split the string up using our closing tag. */
let closingTagSplitArray = self.components(separatedBy: closingTag)
/* Make a placeholder array. */
var allTagsSplitArray = [String]()
/* Iterate through our split array. */
for item in closingTagSplitArray {
if item.contains(openingTag) {
/* Strip the opening tag & append. */
allTagsSplitArray.append(contentsOf: item.components(separatedBy: openingTag))
} else {
/* Just append. */
allTagsSplitArray.append(item)
}
}
/* Instantiate our attributed string. */
let finalAttrStr = NSMutableAttributedString()
for i in stride(from: 0, to: allTagsSplitArray.count, by: 1) {
/* Add our font to every-other item in the array (the tagged portions). */
finalAttrStr.append(NSMutableAttributedString(string: allTagsSplitArray[i], attributes: [NSFontAttributeName : i % 2 == 0 ? untaggedTextFont : taggedTextFont]))
}
return finalAttrStr
}
Note: In this example, I take in an UIFont parameter, but you can modify this function to take in whatever attribute types you want.

Related

iOS Swift Change Particular Text Color Inside Label Programatically

I'm having some search option in my app, which will highlight given word in UISearchBar. Given word may occurs multiple time in label I need t highlight all those words. How it is possible, I have tried with some set of codes but It will highlight only one occurrence of that word, Here is my sample code:
var SearchAttributeText = "The"
let range = (TextValue as NSString).range(of: SearchAttributeText)
let attribute = NSMutableAttributedString.init(string: TextValue)
attribute.addAttribute(NSForegroundColorAttributeName, value: UIColor.red , range: range)
self.label.attributedText = attribute
Need to support with both Upper and lower cases. Word The may occurs multiple time need to highlight all.
You can use following code to search in string
//Text need to be searched
let SearchAttributeText = "the"
//Store label text in variable as NSString
let contentString = lblContent.text! as NSString
//Create range of label text
var rangeString = NSMakeRange(0, contentString.length)
//Convert label text into attributed string
let attribute = NSMutableAttributedString.init(string: contentString as String)
while (rangeString.length != NSNotFound && rangeString.location != NSNotFound) {
//Get the range of search text
let colorRange = (lblContent.text?.lowercased() as! NSString).range(of: SearchAttributeText, options: NSString.CompareOptions(rawValue: 0), range: rangeString)
if (colorRange.location == NSNotFound) {
//If location is not present in the string the loop will break
break
} else {
//This line of code colour the searched text
attribute.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.red , range: colorRange)
lblContent.attributedText = attribute
//This line of code increment the rangeString variable
rangeString = NSMakeRange(colorRange.location + colorRange.length, contentString.length - (colorRange.location + colorRange.length))
}
}
The below line of code update the range by incrementing the location and length parameter of NSRange
rangeString = NSMakeRange(colorRange.location + colorRange.length, contentString.length - (colorRange.location + colorRange.length))

Add '...' After UILabel Word Wrapped Property

I have a UILabel that has max-lines of 2, and a word wrapping property. This is done in storyboard.
I need to add a '...' after the last wrapped word on those labels that end up being word wrapped.
Is this possible? I have tried some solutions from around the internet, but they seem to have not worked. Those include:
Testing label if it has been truncated, and appending '...' to those that have been.
Programmatically using attributed text to hijack storyboard.
Tried using Truncate Tail - Unable to use this because it cuts the word off like so "Highli...".
I think I understand what you are trying to do. This is a bit sloppy, but it should work
extension UILabel {
func truncateAndFitText()
{
if let string = self.text
{
let words = string.components(separatedBy: " ")
var lastString = ""
var tempString = ""
for word in words
{
(tempString == "") ? tempString.append(word) : tempString.append(" \(word)")
let size: CGSize = tempString.size(attributes: [NSFontAttributeName: self.font])
if (size.width > (self.bounds.size.width * CGFloat(self.numberOfLines)))
{
lastString.append("...")
break
}
else
{
lastString = tempString
}
}
self.text = lastString
}
}
}
and then use it like
myLabel.truncateAndFitText

Syntax-highlighting Text With a Custom List of Keywords

I'm working on a macOS application. I need to syntax-highlight text that is placed over TextView (NSTextView) with a list of selected words. For simplicity, I'm actually testing the same feature on the iPhone Simulator. Anyway, a list of words to highlight comes as a form of an array. The following is what I have.
func HighlightText {
let tagArray = ["let","var","case"]
let style = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
style.alignment = NSTextAlignment.Left
let words = textView.string!.componentsSeparatedByString(" ") // textView.text (UITextView) or textView.string (NSTextView)
let attStr = NSMutableAttributedString()
for i in 0..<words.count {
let word = words[i]
if HasElements.containsElements(tagArray,text: word,ignore: true) {
let attr = [
NSForegroundColorAttributeName: syntaxcolor,
NSParagraphStyleAttributeName: style,
]
let str = (i != words.count-1) ? NSAttributedString(string: word.stringByAppendingString(" "), attributes: attr) : NSAttributedString(string: word, attributes: attr)
attStr.appendAttributedString(str)
} else {
let attr = [
NSForegroundColorAttributeName: NSColor.blackColor(),
NSParagraphStyleAttributeName: style,
]
let str = (i != words.count-1) ? NSAttributedString(string: word.stringByAppendingString(" "), attributes: attr) : NSAttributedString(string: word, attributes: attr)
attStr.appendAttributedString(str)
}
}
textView.textStorage?.setAttributedString(attStr)
}
class HasElements {
static func containsElements(array:Array<String>,text:String,ignore:Bool) -> Bool {
var has = false
for str in array {
if str == text {
has = true
}
}
return has
}
}
The simple methodology here is to separate the entire string of text into words with a white space (" ") and puts each word in an array (words). The containsElements function simply tells whether or not the selected word contains one of the keywords in the array (tagArray). If it returns true, the word is put in an NSMutableAttributedString with a highlight color. Otherwise, it's put in the same attributed string with a plain color.
The problem with this simple methodology is that a separated word puts the last word and /n and the next word together. For example, if I have a string like
let base = 3
let power = 10
var answer = 1
, only the first 'let' will be highlighted as the code puts 3 and the next let together like '3\nlet.' If I separate any word containing \n with a fast enumeration, the code won't detect each new paragraph well. I appreciate any advice to make it better. Just FYI, I'm going to leave this topic open to both macOS and iOS.
Muchos thankos
Couple different options. String has a function called componentsSeparatedByCharactersInSet that allows you to separate by a character set you define. Unfortunately this won't work since you want to separate by \n which is more than one character.
You could split the words twice.
let firstSplit = textView.text!.componentsSeparatedByString(" ")
var words = [String]()
for word in firstSplit {
let secondSplit = word.componentsSeparatedByString("\n")
words.appendContentsOf(secondSplit)
}
But then you wouldn't have any sense of the line breaks.. You'd need to re add them back in.
Finally, the easiest hack is simply:
let newString = textView.text!.stringByReplacingOccurrencesOfString("\n", withString: "\n ")
let words = newString.componentsSeparatedByString(" ")
So basically you add your own spaces.

Are there any analogues of [NSString stringWithFormat:] for NSAttributedString

Usually I build app interface in interface builder. Sometimes design requires to use attributed strings (fonts, colors and etc.). It's easy to configure if string is static.
But if string is dynamic (format with arguments) then there are no ways to configure attributes in interface builder. It requires to write a lot of code.
I am looking for some analogues of [NSString stringWithFormat:] for NSAttributedString. So I will be able to set string format and necessary attributes in interface builder, and then provide necessary arguments in code.
For example:
Let's consider that I need display string with such format: "%d + %d = %d" (all numbers are bold).
I want to configure this format in interface builder. In code I want to provide arguments: 1, 1, 2. App should show "1 + 1 = 2".
Compatible with Swift 4.2
public extension NSAttributedString {
convenience init(format: NSAttributedString, args: NSAttributedString...) {
let mutableNSAttributedString = NSMutableAttributedString(attributedString: format)
args.forEach { (attributedString) in
let range = NSString(string: mutableNSAttributedString.string).range(of: "%#")
mutableNSAttributedString.replaceCharacters(in: range, with: attributedString)
}
self.init(attributedString: mutableNSAttributedString)
}
}
Usage:
let content = NSAttributedString(string: "The quick brown %# jumps over the lazy %#")
let fox = NSAttributedString(string: "fox", attributes: [.font: Fonts.CalibreReact.boldItalic.font(size: 40)])
let dog = NSAttributedString(string: "dog", attributes: [.font: Fonts.CalibreReact.lightItalic.font(size: 11)])
attributedLabel.attributedText = NSAttributedString(format: content, args: fox, dog)
Result:
I was looking for good existing solution for this question, but no success.
So I was able to implement it on my own.
That's why I am self-answering the question to share the knowledge with community.
Solution
NSAttributedString+VPAttributedFormat category provides methods for building attributed string based on attributed format and arguments that should satisfy this format.
The most suitable case of using this category is text controls with variable attributed text configured in interface builder.
You need set correct string format to attributed text and configure necessary attributes.
Then you need pass necessary arguments in code by using methods of this category.
Format syntax is the same as in [NSString stringWithFormat:] method;
Can be used in Objective C and Swift code;
Requires iOS 6.0 and later;
Integrated with CocoaPods;
Covered with unit tests.
Usage
1. Import framework header or module
// Objective C
// By header
#import <VPAttributedFormat/VPAttributedFormat.h>
// By module
#import VPAttributedFormat;
// Swift
import VPAttributedFormat
2. Set correct format and attributes for text control in interface builder
3. Create IBOutlet and link it with text control
// Objective C
#property (nonatomic, weak) IBOutlet UILabel *textLabel;
// Swift
#IBOutlet weak var textLabel: UILabel!
4. Populate format with necessary arguments
// Objective C
NSString *hot = #"Hot";
NSString *cold = #"Cold";
self.textLabel.attributedText = [NSAttributedString vp_attributedStringWithAttributedFormat:self.textLabel.attributedText,
hot,
cold];
// Swift
let hot = "Hot"
let cold = "Cold"
var arguments: [CVarArgType] = [hot, cold]
textLabel.attributedText = withVaList(arguments) { pointer in
NSAttributedString.vp_attributedStringWithAttributedFormat(textLabel.attributedText, arguments: pointer)
}
5. See result
Examples
VPAttributedFormatExample is an example project. It provides Basic and Pro format examples.
Here's a category I wrote to add the method to NSAttributedString. You'll have to pass in NULL as the last argument to the function however, otherwise it will crash to the va_list restrictions on detecting size. [attributedString stringWithFormat:attrFormat, attrArg1, attrArg2, NULL];
#implementation NSAttributedString(stringWithFormat)
+(NSAttributedString*)stringWithFormat:(NSAttributedString*)format, ...{
va_list args;
va_start(args, format);
NSMutableAttributedString *mutableAttributedString = (NSMutableAttributedString*)[format mutableCopy];
NSString *mutableString = [mutableAttributedString string];
while (true) {
NSAttributedString *arg = va_arg(args, NSAttributedString*);
if (!arg) {
break;
}
NSRange rangeOfStringToBeReplaced = [mutableString rangeOfString:#"%#"];
[mutableAttributedString replaceCharactersInRange:rangeOfStringToBeReplaced withAttributedString:arg];
}
va_end(args);
return mutableAttributedString;
}
#end
Here is a Swift 4 extension based on TheJeff's answer (corrected for multiple substitutions). It is restricted to substituting placeholders with NSAttributedString's:
public extension NSAttributedString {
convenience init(format: NSAttributedString, args: NSAttributedString...) {
let mutableNSAttributedString = NSMutableAttributedString(attributedString: format)
var nsRange = NSString(string: mutableNSAttributedString.string).range(of: "%#")
var param = 0
while nsRange.location != NSNotFound {
guard args.count > 0, param < args.count else {
fatalError("Not enough arguments provided for \(format)")
}
mutableNSAttributedString.replaceCharacters(in: nsRange, with: args[param])
param += 1
nsRange = NSString(string: mutableNSAttributedString.string).range(of: "%#")
}
self.init(attributedString: mutableNSAttributedString)
}
}

How can I concatenate NSAttributedStrings?

I need to search some strings and set some attributes prior to merging the strings, so having NSStrings -> Concatenate them -> Make NSAttributedString is not an option, is there any way to concatenate attributedString to another attributedString?
I'd recommend you use a single mutable attributed string a #Linuxios suggested, and here's another example of that:
NSMutableAttributedString *mutableAttString = [[NSMutableAttributedString alloc] init];
NSString *plainString = // ...
NSDictionary *attributes = // ... a dictionary with your attributes.
NSAttributedString *newAttString = [[NSAttributedString alloc] initWithString:plainString attributes:attributes];
[mutableAttString appendAttributedString:newAttString];
However, just for the sake of getting all the options out there, you could also create a single mutable attributed string, made from a formatted NSString containing the input strings already put together. You could then use addAttributes: range: to add the attributes after the fact to the ranges containing the input strings. I recommend the former way though.
If you're using Swift, you can just overload the + operator so that you can concatenate them in the same way you concatenate normal strings:
// concatenate attributed strings
func + (left: NSAttributedString, right: NSAttributedString) -> NSAttributedString
{
let result = NSMutableAttributedString()
result.append(left)
result.append(right)
return result
}
Now you can concatenate them just by adding them:
let helloworld = NSAttributedString(string: "Hello ") + NSAttributedString(string: "World")
Swift 3: Simply create a NSMutableAttributedString and append the attributed strings to them.
let mutableAttributedString = NSMutableAttributedString()
let boldAttribute = [
NSFontAttributeName: UIFont(name: "GothamPro-Medium", size: 13)!,
NSForegroundColorAttributeName: Constants.defaultBlackColor
]
let regularAttribute = [
NSFontAttributeName: UIFont(name: "Gotham Pro", size: 13)!,
NSForegroundColorAttributeName: Constants.defaultBlackColor
]
let boldAttributedString = NSAttributedString(string: "Warning: ", attributes: boldAttribute)
let regularAttributedString = NSAttributedString(string: "All tasks within this project will be deleted. If you're sure you want to delete all tasks and this project, type DELETE to confirm.", attributes: regularAttribute)
mutableAttributedString.append(boldAttributedString)
mutableAttributedString.append(regularAttributedString)
descriptionTextView.attributedText = mutableAttributedString
swift5 upd:
let captionAttribute = [
NSAttributedString.Key.font: Font.captionsRegular,
NSAttributedString.Key.foregroundColor: UIColor.appGray
]
Try this:
NSMutableAttributedString* result = [astring1 mutableCopy];
[result appendAttributedString:astring2];
Where astring1 and astring2 are NSAttributedStrings.
2020 | SWIFT 5.1:
You're able to add 2 NSMutableAttributedString by the following way:
let concatenated = NSAttrStr1.append(NSAttrStr2)
Another way works with NSMutableAttributedString and NSAttributedString both:
[NSAttrStr1, NSAttrStr2].joinWith(separator: "")
Another way is....
var full = NSAttrStr1 + NSAttrStr2 + NSAttrStr3
and:
var full = NSMutableAttributedString(string: "hello ")
// NSAttrStr1 == 1
full += NSAttrStr1 // "hello 1"
full += " world" // "hello 1 world"
You can do this with the following extension:
// works with NSAttributedString and NSMutableAttributedString!
public extension NSAttributedString {
static func + (left: NSAttributedString, right: NSAttributedString) -> NSAttributedString {
let leftCopy = NSMutableAttributedString(attributedString: left)
leftCopy.append(right)
return leftCopy
}
static func + (left: NSAttributedString, right: String) -> NSAttributedString {
let leftCopy = NSMutableAttributedString(attributedString: left)
let rightAttr = NSMutableAttributedString(string: right)
leftCopy.append(rightAttr)
return leftCopy
}
static func + (left: String, right: NSAttributedString) -> NSAttributedString {
let leftAttr = NSMutableAttributedString(string: left)
leftAttr.append(right)
return leftAttr
}
}
public extension NSMutableAttributedString {
static func += (left: NSMutableAttributedString, right: String) -> NSMutableAttributedString {
let rightAttr = NSMutableAttributedString(string: right)
left.append(rightAttr)
return left
}
static func += (left: NSMutableAttributedString, right: NSAttributedString) -> NSMutableAttributedString {
left.append(right)
return left
}
}
If you're using Cocoapods, an alternative to both above answers that let you avoid mutability in your own code is to use the excellent NSAttributedString+CCLFormat category on NSAttributedStrings that lets you write something like:
NSAttributedString *first = ...;
NSAttributedString *second = ...;
NSAttributedString *combined = [NSAttributedString attributedStringWithFormat:#"%#%#", first, second];
It of course it just uses NSMutableAttributedString under the covers.
It also has the extra advantage of being a fully fledged formatting function — so it can do a lot more than appending strings together.
// Immutable approach
// class method
+ (NSAttributedString *)stringByAppendingString:(NSAttributedString *)append toString:(NSAttributedString *)string {
NSMutableAttributedString *result = [string mutableCopy];
[result appendAttributedString:append];
NSAttributedString *copy = [result copy];
return copy;
}
//Instance method
- (NSAttributedString *)stringByAppendingString:(NSAttributedString *)append {
NSMutableAttributedString *result = [self mutableCopy];
[result appendAttributedString:append];
NSAttributedString *copy = [result copy];
return copy;
}
You can try SwiftyFormat
It uses following syntax
let format = "#{{user}} mentioned you in a comment. #{{comment}}"
let message = NSAttributedString(format: format,
attributes: commonAttributes,
mapping: ["user": attributedName, "comment": attributedComment])
private var fillAttributes:[NSMutableAttributedString.Key : Any]? = nil
fontAttributes = [.foregroundColor : SKColor.red,
.strokeWidth : 0.0,
.font : CPFont(name: "Verdana-Bold",
.size : 50,]
fontAttributes.updateValue(SKColor.green, forKey: .foregroundColor)

Resources