Skip to content

Instantly share code, notes, and snippets.

@lionhylra
Last active January 20, 2020 04:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lionhylra/d70d98de6af27d801cba4e34a9838383 to your computer and use it in GitHub Desktop.
Save lionhylra/d70d98de6af27d801cba4e34a9838383 to your computer and use it in GitHub Desktop.
A NSAttributedString extension that adds a convenient method for composing a NSAttributedString with links and images.
import UIKit
// How to use:
/*
let label = UILabel()
let textView = UITextView()
label.attributedText = .compose(
"Please go to ",
.link(label: "Google",
url: URL(string: "https://google.com")!),
" ", .systemImage("globe")
)
let style = NSAttributedStringStyle(font: .preferredFont(forTextStyle: .body), lineSpacing: 16)
textView.attributedText = .compose(style: style,
"Please go to ", .linkLiteral("https://google.com"), " for more details.", .systemImage("globe"),
.lineBreak,
"Also you can go to our ", .link(label: "Website", url: URL(string: "http://google.com")!), ".",
.lineBreak,
"This is another line.", .systemImage("arrow.up.bin")
)
*/
extension NSAttributedString {
static func compose(style: NSAttributedStringStyle = .default, _ components: NSAttributedStringComponent...) -> NSAttributedString {
let mutableAttributedString = NSMutableAttributedString()
for c in components {
mutableAttributedString.append(c.attributedString())
}
let range = NSRange(location: 0, length: mutableAttributedString.length)
mutableAttributedString.addAttributes(style.attributes(), range: range)
return mutableAttributedString
}
}
struct NSAttributedStringStyle {
static let `default` = NSAttributedStringStyle()
let font: UIFont?
let foregroundColor: UIColor?
let alignment: NSTextAlignment?
let lineSpacing: CGFloat?
let paragraphSpacing: CGFloat?
init(font: UIFont? = nil, foregroundColor: UIColor? = nil, alignment: NSTextAlignment? = nil, lineSpacing: CGFloat? = nil, paragraphSpacing: CGFloat? = nil) {
self.font = font
self.foregroundColor = foregroundColor
self.alignment = alignment
self.lineSpacing = lineSpacing
self.paragraphSpacing = paragraphSpacing
}
func attributes() -> [NSAttributedString.Key: Any] {
var attributes: [NSAttributedString.Key: Any] = [:]
attributes[.font] = font
attributes[.foregroundColor] = foregroundColor
let paragraphStyle = NSMutableParagraphStyle()
if let alignment = alignment {
paragraphStyle.alignment = alignment
}
if let lineSpacing = lineSpacing {
paragraphStyle.lineSpacing = lineSpacing
}
if let paragraphSpacing = paragraphSpacing {
paragraphStyle.paragraphSpacing = paragraphSpacing
}
attributes[.paragraphStyle] = paragraphStyle
return attributes
}
}
enum NSAttributedStringComponent {
case text(String)
case link(label: String, url: URL)
case linkLiteral(String)
case image(UIImage?, CGSize? = nil)
@available(iOS 13.0, *)
case systemImage(String, tintColor: UIColor? = nil)
case lineBreak
case space
func attributedString() -> NSAttributedString {
switch self {
case .text(let string):
return NSAttributedString(string: string)
case .link(let label, let url):
return NSAttributedString(string: label, attributes: [.link: url])
case .linkLiteral(let string):
assert(URL(string: string) != nil)
return Self.link(label: string, url: URL(string: string)!).attributedString()
case .image(let image, let size):
let attachment = NSTextAttachment()
attachment.image = image
if let size = size {
attachment.bounds.size = size
}
return NSAttributedString(attachment: attachment)
case .systemImage(let name, let tintColor):
if #available(iOS 13.0, *) {
var image = UIImage(systemName: name)
if let tintColor = tintColor {
image = image?.withTintColor(tintColor)
}
return Self.image(image).attributedString()
} else {
let image = UIImage(named: name)
return Self.image(image).attributedString()
}
case .lineBreak:
return NSAttributedString(string: "\n")
case .space:
return Self.text(" ").attributedString()
}
}
}
extension NSAttributedStringComponent: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self = .text(value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment