Swift: Refactoring NSMutableAttributedString - ios

I have used NSMutableAttributedString/NSAttributedString here and there but don't have extensive experience with them. I have a code block that repeats itself and was wondering how would I go about refactoring it? I've been working on a few extensions to refactor this but haven't had any luck.
The attributes goes into a UILabel variable closure.
let attributes = NSMutableAttributedString(string: "ID: \n",
attributes: [NSAttributedString.Key.foregroundColor : UIColor.black,
NSAttributedString.Key.backgroundColor : UIColor.clear,
NSAttributedString.Key.font : UIFont(name: "Helvetica", size: 15)!])
attributes.append(NSMutableAttributedString(string: "\(nameID)",
attributes: [NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.backgroundColor : UIColor.customBlue(),
NSAttributedString.Key.font : UIFont(name: "Helvetica", size: 15)!]))
attributes.append(NSMutableAttributedString(string: "\nDate Created: \n",
attributes: [NSAttributedString.Key.foregroundColor : UIColor.black,
NSAttributedString.Key.backgroundColor : UIColor.clear,
NSAttributedString.Key.font : UIFont(name: "Helvetica", size: 15)!]))
attributes.append(NSMutableAttributedString(string: "TEST",
attributes: [NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.backgroundColor : UIColor.customBlue(),
NSAttributedString.Key.font : UIFont(name: "Helvetica", size: 15)!]))
attributes.append(NSMutableAttributedString(string: "\nDate Last Used: \n",
attributes: [NSAttributedString.Key.foregroundColor : UIColor.black,
NSAttributedString.Key.backgroundColor : UIColor.clear,
NSAttributedString.Key.font : UIFont(name: "Helvetica", size: 15)!]))
attributes.append(NSMutableAttributedString(string: "TEST",
attributes: [NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.backgroundColor : UIColor.customBlue(),
NSAttributedString.Key.font : UIFont(name: "Helvetica", size: 15)!]))

Try looking at your code and seeing if there's a pattern. What I see is creating multiple NSAttributedStrings with String values, setting foreground and background, and the same font over and over. So this is ripe for the refactor picking.
First, set up your data:
// one note here is that you actually have a newline char for every entry, so it's probably better practice t simply drop them and apply them in your loop which we'll get to
let values = ["ID:", nameID /*I assume this is already a `String`*/, "Date Created: ", "TEST", "Date Last Used:", "Test"]
Colors seem to follow their own pattern: titles are black on clear and values are white on custom blue. We can use this in our loop, too. Let's set up our attributes so they're easy to reference:
let titleAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.black,
.backgroundColor: UIColor.clear,
.font: UIFont(name: "Helvetica", size: 15)!
]
let valueAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.white,
.backgroundColor: UIColor.customBlue(),
.font: UIFont(name: "Helvetica", size: 15)!
]
Now, let's loop:
let mutableString = NSMutableAttributedString()
for (index, currentValue) in values.enumerated() {
// if we're on an even number, we have a title. If it's odd, it's a value
let attributes = index % 2 == 0 ? titleAttributes : valueAttributes
// if we aren't on the last index, add a newline to the value
let value = index < values.count - 1 ? "\(currentValue)\n" : currentValue
mutableString.appendAttributedString(string: value, attributes: attributes)
}

Related

How to properly apply stroke for non-latin characters in Swift?

I'm working on a project to apply text attributes to UIlabel text. I'm using the common NSAttributedString keys to basically apply strokeColor and strokeWidth and foregroundColor to generate the desired outlining effect. The problem appears on non-latin characters, such as Arabic, where letters are individual highlighted instead of the entire work. The letter typically are connected in Arabic, unlink english were letters are spaced. I'm attaching the example I'm working on with a screenshot of the issue and the desired outcome. I would appreciate your support and suggestions.
let quote = "الكتابة على الصور بخطوط جميلة"
//let font = UIFont.systemFon.selft(ofSize: 50)
let attributes: [NSAttributedString.Key: Any] = [
.strokeColor: UIColor.green,
.strokeWidth: -3.0,
.foregroundColor: UIColor.red,
.font: UIFont(name: "Georgia-Bold", size: 40)!,
]
let attributedQuote = NSAttributedString(string: quote, attributes: attributes)
TextLabel.attributedText = attributedQuote
outcome with issue:
desired outcome:
Taking a quick look at this - and comparing it to similar output from css - I think you'll need to use two labels layers on top of each other.
Give this a try:
let backLabel = UILabel()
let frontLabel = UILabel()
backLabel.translatesAutoresizingMaskIntoConstraints = false
frontLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(backLabel)
view.addSubview(frontLabel)
NSLayoutConstraint.activate([
backLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0),
backLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
backLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0),
frontLabel.topAnchor.constraint(equalTo: backLabel.topAnchor),
frontLabel.leadingAnchor.constraint(equalTo: backLabel.leadingAnchor),
frontLabel.trailingAnchor.constraint(equalTo: backLabel.trailingAnchor),
])
let quote = "الكتابة على الصور بخطوط جميلة"
var attributes: [NSAttributedString.Key: Any] = [
.strokeColor: UIColor.green,
.strokeWidth: 12.0,
.foregroundColor: UIColor.red,
.font: UIFont(name: "Georgia-Bold", size: 40)!,
]
var attributedQuote = NSAttributedString(string: quote, attributes: attributes)
backLabel.attributedText = attributedQuote
attributes = [
.strokeColor: UIColor.green,
.strokeWidth: 0,
.foregroundColor: UIColor.red,
.font: UIFont(name: "Georgia-Bold", size: 40)!,
]
attributedQuote = NSAttributedString(string: quote, attributes: attributes)
frontLabel.attributedText = attributedQuote
Result:

How to add Stroke in UILabel Swift IOS

While add Stroke to UILabel App Crashes
It Crash due to found nil
let strokeTextAttributes = [
NSAttributedStringKey.strokeColor : UIColor.green,
NSAttributedStringKey.foregroundColor : UIColor.lightGray,
NSAttributedStringKey.strokeWidth : -4.0,
NSAttributedStringKey.font : UIFont.boldSystemFont(ofSize: 52)
] as [NSAttributedStringKey : Any]
hello_cell_lb.attributedText = NSAttributedString(string: "\(hello_array[indexPath.row])", attributes: strokeTextAttributes)
You seem to add stroke to a nil label in cell but it works here

foregroundColor for Textfield in swift

Dears,
I'm learning swift and I'm trying to color a textfield text, the issue now is that the color is not applied to the text. please find the code next:
let textStyle:[String:Any] = [
NSAttributedStringKey.strokeColor.rawValue: UIColor.black,
NSAttributedStringKey.foregroundColor.rawValue: UIColor.white,
NSAttributedStringKey.font.rawValue: UIFont (name: "Impact", size: 50)!,
NSAttributedStringKey.strokeWidth.rawValue: 3.5
]
Thanks
The key appears to be the .strokeWidth... If you want stroke and fill, the stroke width needs to be a negative value.
Give this a try:
let textStyle = [
NSAttributedStringKey.strokeColor: UIColor.black,
NSAttributedStringKey.foregroundColor: UIColor.white,
NSAttributedStringKey.font: UIFont(name: "Impact", size: 50)!,
NSAttributedStringKey.strokeWidth: -3.5
]
can you try this...
myTextField.attributedText = NSAttributedString(string: "text",
attributes: [NSAttributedStringKey.foregroundColor: UIColor.white])

Outline UILabel text in UILabel Subclass

I'm trying hard to find a way to simply add an outline/stroke/contour to my UILabel text. Talking about a stroke around the letters of the text not around the background of a UILabel.
I'm using swift 3 and I'd like to outline my text directly into my subclass: UILabel.
I found multiple answers suggesting this way to do things :
let strokeTextAttributes = [
NSStrokeColorAttributeName : UIColor.black,
NSForegroundColorAttributeName : UIColor.white,
NSStrokeWidthAttributeName : -4.0,
NSFontAttributeName : UIFont.boldSystemFont(ofSize: 30)
]
self.attributedText = NSMutableAttributedString(string: self.text!, attributes: strokeTextAttributes)
But the thing is that it doesn't work. My text is still the same with no outline...
Could anyone help me here ?
That would be a great thing :)
Thanks a lot. Cheers guys.
This code works for me.
Swift 3
let strokeTextAttributes = [
NSStrokeColorAttributeName : UIColor.black,
NSForegroundColorAttributeName : UIColor.white,
NSStrokeWidthAttributeName : -4.0,
NSFontAttributeName : UIFont.boldSystemFont(ofSize: 30)
] as [String : Any]
myLabel.attributedText = NSMutableAttributedString(string: "Test me i have color.", attributes: strokeTextAttributes)
Swift 4.2 & 5.1
let strokeTextAttributes = [
NSAttributedString.Key.strokeColor : UIColor.red,
NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.strokeWidth : -4.0,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 30)]
as [NSAttributedString.Key : Any]
labelOutLine.attributedText = NSMutableAttributedString(string: "Your outline text", attributes: strokeTextAttributes)
#anandnimje answer converted to Swift 4.2 and wrapped it into a function:
public func stroke(font: UIFont, strokeWidth: Float, insideColor: UIColor, strokeColor: UIColor) -> [NSAttributedStringKey: Any]{
return [
NSAttributedStringKey.strokeColor : strokeColor,
NSAttributedStringKey.foregroundColor : insideColor,
NSAttributedStringKey.strokeWidth : -strokeWidth,
NSAttributedStringKey.font : font
]
}
Usage:
label.attributedText = NSMutableAttributedString(string: "Hello World",
attributes: stroke(font: UIFont(name: "SourceSansPro-Black", size: 20)!,
strokeWidth: 4, insideColor: .white, strokeColor: .black))
Make sure you have the right name for your UIFont, else it crashes. Should never be a problem if you have the right name.
Here you have class with implementation, copy and paste to playgrond for test:
class StrokedLabel: UILabel {
var strockedText: String = "" {
willSet(newValue) {
let strokeTextAttributes = [
NSStrokeColorAttributeName : UIColor.black,
NSForegroundColorAttributeName : UIColor.white,
NSStrokeWidthAttributeName : -4.0,
NSFontAttributeName : UIFont.boldSystemFont(ofSize: 30)
] as [String : Any]
let customizedText = NSMutableAttributedString(string: newValue,
attributes: strokeTextAttributes)
attributedText = customizedText
}
}
}
//////////// PLAYGROUND IMPLEMENTATION PART /////////
let text = "Stroked text"
// UILabel subclass initialization
let label = StrokedLabel(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
// simple assign String to 'strockedText' property to see the results
label.strockedText = text
label.backgroundColor = UIColor.white
label
Swift 4.2
import UIKit
class StrokedLabel: UILabel {
var strockedText: String = "" {
willSet(newValue) {
let strokeTextAttributes : [NSAttributedString.Key : Any] = [
NSAttributedString.Key.strokeColor : UIColor.black,
NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.strokeWidth : -4.0,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 30)
] as [NSAttributedString.Key : Any]
let customizedText = NSMutableAttributedString(string: newValue,
attributes: strokeTextAttributes)
attributedText = customizedText
}
}
}
//////////// PLAYGROUND IMPLEMENTATION PART /////////
let text = "Stroked text"
// UILabel subclass initialization
let label = StrokedLabel(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
// simple assign String to 'strockedText' property to see the results
label.strockedText = text
label.backgroundColor = UIColor.clear
label
Maybe refactoring for this class will be welcomed, but should work for you at this form
As you can see usage is very convenient. 👾
Update to Swift 5
This answer is built on Anandnimje and J.Doe answers, and is meant to update and streamline it to make the usage clearer and simpler.
Simply use these two functions:
func outline(string:String, font:String, size:CGFloat, outlineSize:Float, textColor:UIColor, outlineColor:UIColor) -> NSMutableAttributedString {
return NSMutableAttributedString(string:string,
attributes: outlineAttributes(font: UIFont(name: font, size: size)!,
outlineSize: outlineSize, textColor: textColor, outlineColor: outlineColor))
}
func outlineAttributes(font: UIFont, outlineSize: Float, textColor: UIColor, outlineColor: UIColor) -> [NSAttributedString.Key: Any]{
return [
NSAttributedString.Key.strokeColor : outlineColor,
NSAttributedString.Key.foregroundColor : textColor,
NSAttributedString.Key.strokeWidth : -outlineSize,
NSAttributedString.Key.font : font
]
}
Then use outline with your labels as the following:
label.attributedText = outline(string: "Label Text", font: "HelveticaNeue", size: 14, outlineSize: 4, textColor: .white, outlineColor: .black)
Below is what I used in my App written in Swift 4.1
Swift 4.x
let strokeTextAttributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.strokeColor: UIColor.black,
NSAttributedStringKey.foregroundColor : UIColor.white,
NSAttributedStringKey.strokeWidth : -3.0,
NSAttributedStringKey.font : UIFont.boldSystemFont(ofSize: 18)
]
Your code works for me if I set the type of the attributes dictionary, like this:
let strokeTextAttributes: [String: Any] = [
// etc...
]
Maybe that's all your missing?

Could not find an overload for “init” that accepts the supplied arguments SWIFT

I used this code
self.navigationController?.navigationBar.titleTextAttributes =
[NSFontAttributeName: UIFont(name: "HelveticaNeue-Light", size: 20),
NSForegroundColorAttributeName: UIColor.whiteColor()]
and I'm getting error "Could not find an overload for “init” that accepts the supplied arguments"
UIFont(name:size:) is now a failable initializer -- it will return nil if it can't find that font and crash your app if you unwrap the return value. Use this code to safely get the font and use it:
if let font = UIFont(name: "HelveticaNeue-Light", size: 20) {
self.navigationController?.navigationBar.titleTextAttributes =
[NSFontAttributeName: font,
NSForegroundColorAttributeName: UIColor.whiteColor()]
}
Use this
self.navigationController?.navigationBar.titleTextAttributes =
[NSFontAttributeName: UIFont(name: "HelveticaNeue-Light", size: 20)!,
NSForegroundColorAttributeName: UIColor.whiteColor()!]
or this one
if let font = UIFont(name:"HelveticaNeue-Light", size: 20.0) {
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor(), NSFontAttributeName: font]
}
Another approach is to build up a dictionary before setting titleTextAttributes. This just avoids you the else(s), which would be more beneficial in cases where you wanted to set further parameters also using failable initialisers. Eg:
var attributes : [NSObject : AnyObject] = [NSForegroundColorAttributeName : UIColor.whiteColor()]
if let font = UIFont(name: "Helvetica", size: 20) {
attributes[NSFontAttributeName] = font
}
if let someData = NSData(contentsOfFile: "dataPath") {
attributes["imageData"] = someData
}
self.myObject.attributes = attributes

Resources