AttributedString
Characters + Ranges + Dictionary
기존 NSAttributedString을 대체하는 AttributedString 타입 추가
var thanks = AttributedString("Thank You!")
thanks.font = .body.bold()
var website = AttributedString("Please visit our website.")
website.font = .body.italic()
website.link = URL(string: "<http://www.example.com>")
var container = AttributeContainer(
if important {
container.foregroundColor = .red
container.underlineColor = .primary
} else {
container.foregroundColor = .primary
}
thanks.mergeAttributes(container)
website.mergeAttributes(container)
// characters
let characterView = message.characters
for i in characterView.indices where characterView[i].isPunctuation {
message[i..<characterView.index(after: i)].foregroundColor = .orange
}
// runs
let runCount = message.runs.count
let firstRun = message.runs.first!
let firstString = String(message.characters[firstRun.range])
// 특정 Attribute 기준으로만 다루기
let linkRunCount = message.runs[\\.link].count
var insecureLinks: [URL] = []
for (value, range) in message.runs[\\.link] {
if let v = value, v.scheme != "https" {
insecureLinks.append(v)
}
}
// mutation
if let range = message.range(of: "visit") {
message[range].font = .body.italic().bold()
message.characters.replaceSubrange(range, with: "surf")
}
func prompt(for document: String) -> String {
String(localized: "Would you like to save the document \\"\\(document)\\"?")
}
func attributedPrompt(for document: String) -> AttributedString {
AttributedString(localized: "Would you like to save the document \\"\\(document)\\"?")
}
NSAttributedString - 생성자에 그냥 넣으면 바꿔준다.
Coding - 기본적으로 Codable을 채택했기 때문에 신경쓸 것은 없다.
커스텀 Attribute
enum RainbowAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { // Codable이 필요한 경우
enum Value: String, Codable { // associatedType
case plain
case fun
case extreme
}
public static var name = "rainbow"
}
This text contains [a link](<http://www.example.com>)
This text contains ![an image](<http://www.example.com/my_image.gif>).
// 여기까지는 표준 마크다운
// Custom Attribute, JSON 5를 기준으로 만들어짐
This text contains ^[an attribute](rainbow: 'extreme')
This text contains ^[two attributes](rainbow: 'extreme', otherValue: 42).
This text contains ^[an attribute with 2 properties](someStuff: { key: true, key2: false}).
extension AttributeScopes {
struct CaffeAppAttributes: AttributeScope {
let rainbow: RainbowAttribute
let swiftUI: SwiftUIAttributes // 시스템 스코프를 포함한다.
}
var caffeApp: CaffeAppAttributes.Type { CaffeAppAttributes.self }
}
let header = AttributedString(localized: "^[Fast & Delicious](rainbow: 'extreme') Food",
including: \\.caffeApp)
Formatters
Date Formatting
let date = Date.now
let formatted = date.formatted() // formatted(.dateTime)과 동일
// "6/7/2021, 9:42AM"
let onlyDate = date.formatted(date: .numeric, time: .omitted)
// onlyDate is "6/7/2021"
let onlyTime = date.formatted(date: .omitted. time: .shortened)
// onlyTime is "9:42 AM"
let formatted = date.formatted(.dateTime.year().day().month())
// "Jun 7, 2021"
date.formatted(.dateTime.year().day().month(.wide))
// "June 7, 2021"
date.formatted(.dateTime.weekday(.wide))
// "Monday"
date.formatted(.iso8601)
// "20210607T164200Z"
date.formatted(.iso8601.year().month().day().dateSeparator(.dash))
// "2021-06-07"
let now = Date.now
let later = new + TimeInterval(5000)
let range = (now..<later).formatted()
"6/7/21, 9:42 - 11:05AM"
let noDate = (now..<later).formatted(date: .omitted, time: .complete)
"9:42:00 AM PDT - 11:05:20 AM PDT"
let timeDuration = (now..<later).formatted(.timeDuration)
"1:23:20"
let components = (now..<later).formatted(.components(style: .wide))
"1 hour, 23 minutes, 20 seconds"
let relative = later.formatted(.relative(presentation: .named, unitsStyle: .wide))
"in 1 hour"
var str = date.formatted(.dateTime
.minute()
.hour()
.weekday()
.locale(locale)
.attributed)
let weekday = AttributeContainer.dateField(.weekday)
let color = AttributedContainer
.foregroundColor(.orange)
str.replaceAttributes(weekday, with: color)
let format = Date.FormatStyle().year().day().month()
let formatted = date.formatted(format)
// "Jun 7, 2021"
if let date = try? Date(formatted, strategy: format) {
// 2021-06-07 07:00:00 +0000
}
let strategy = Date.ParseStrategy(
format: "\\(year: .defaultDigits)-\\(month: .twoDigits)-\\(day: .twoDigits)",
timeZone: TimeZone.current)
if let date = Date("2021-06-07", strategy: strategy) {
// 2021-06-07 07:00:00 +0000
}
Number formatting
12345.formatted()
//"12,345"
25.formatted(.percent)
// 25%
42e9.formatted(.number.notation(.scientific))
// 4.2E10
29.formatted(.currency(code: "usd"))
// $29.00
let list = [25, 50, 75].formatted(.list(memberStyle: .percent, type: .or))
// "25%, 50%, or 75%"
Grammar agreement