Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2021 EssentialsSystem Services

WWDC21 · 38 min · Essentials / System Services

What’s new in Foundation

Discover how the latest updates to Foundation can help you improve your app’s localization and internationalization support. Find out about the new AttributedString, designed specifically for Swift, and learn how you can use Markdown to apply style to your localized strings. Explore the grammar agreement engine, which automatically fixes up localized strings so they match grammatical gender and pluralization. And we’ll take you through improvements to date and number formatting that simplify complex requirements while also improving performance.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 23 snippets

Attributed String Basics swift · at 2:50 ↗
func attributedStringBasics(important: Bool) {
    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)

    print(thanks)
    print(website)
}
Attributed String Characters swift · at 4:24 ↗
func attributedStringCharacters() {
    var message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._")
    let characterView = message.characters
    for i in characterView.indices where characterView[i].isPunctuation {
        message[i..<characterView.index(after: i)].foregroundColor = .orange
    }

    print(message)
}
Attributed String Runs (Part 1) swift · at 5:12 ↗
func attributedStringRuns() {
    let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._")
    let runCount = message.runs.count
    // runCount is 4
    print(runCount)

    let firstRun = message.runs.first!
    let firstString = String(message.characters[firstRun.range])
    // firstString is "Thank you!"
    print(firstString)
}
Attributed String Runs (Part 2) swift · at 5:49 ↗
func attributedStringRuns2() {
    let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._")

    let linkRunCount = message.runs[\.link].count
    // linkRunCount is 3
    print(linkRunCount)

    var insecureLinks: [URL] = []
    for (value, range) in message.runs[\.link] {
        if let v = value, v.scheme != "https" {
            insecureLinks.append(v)
        }
    }
    // insecureLinks is [http://www.example.com]
    print(insecureLinks)
}
Attributed String Mutation swift · at 6:36 ↗
func attributedStringMutation() {
    var message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._")

    if let range = message.range(of: "visit") {
        message[range].font = .body.italic().bold()
        message.characters.replaceSubrange(range, with: "surf")
    }

    print(message)
}
Localized Strings swift · at 7:29 ↗
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)”?")
}
Codable Attributed Strings swift · at 9:34 ↗
struct FoodItem: Codable {
    // Placeholder type to demonstrate concept
    var name: String
}

struct Receipt: Codable {
    var items: [FoodItem]
    var thankYouMessage: AttributedString
}

func codableBasics() {
    let message = AttributedString(localized: "Thank you! _Please visit our [website](http://www.example.com)._")
    let receipt = Receipt(items: [FoodItem(name: "Juice")], thankYouMessage: message)

    let encoded = try! JSONEncoder().encode(receipt)
    let decodedReceipt = try! JSONDecoder().decode(Receipt.self, from: encoded)

    print("\(decodedReceipt.thankYouMessage)")
}
Markdown Decodable Attribute swift · at 10:42 ↗
enum RainbowAttribute : CodableAttributedStringKey, MarkdownDecodableAttributedStringKey {
    enum Value : String, Codable {
        case plain
        case fun
        case extreme
    }

    public static var name = "rainbow"
}
Custom Markdown Syntax markdown · at 11:30 ↗
This text contains [a link](http://www.example.com).

This text contains ![an image](http://www.example.com/my_image.gif).

This text contains ^[an attribute](rainbow: 'extreme').
Custom Markdown Attributes markdown · at 12:27 ↗
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}).
Attribute Scopes swift · at 13:15 ↗
extension AttributeScopes {
    struct CaffeAppAttributes : AttributeScope {
        let rainbow: RainbowAttribute

        let swiftUI: SwiftUIAttributes
    }

    var caffeApp: CaffeAppAttributes.Type { CaffeAppAttributes.self }
}

func customAttributesFromMarkdown() {
    let header = AttributedString(localized: "^[Fast & Delicious](rainbow: 'extreme') Food", including: \.caffeApp)

    print(header)
}
Formatting Dates swift · at 17:28 ↗
func formattingDates() {
    // Note: This will use your current date & time plus current locale. Example output is for en_US locale.
    let date = Date.now

    let formatted = date.formatted()
    // example: "6/7/2021, 9:42 AM"
    print(formatted)

    let onlyDate = date.formatted(date: .numeric, time: .omitted)
    // example: "6/7/2021"
    print(onlyDate)

    let onlyTime = date.formatted(date: .omitted, time: .shortened)
    // example: "9:42 AM"
    print(onlyTime)
}
Formatting Dates With Styles swift · at 18:16 ↗
func formattingDatesWithStyles() {
    // Note: This will use your current date & time plus current locale. Example output is for en_US locale.
    let date = Date.now

    let formatted = date.formatted(.dateTime)
    // example: "6/7/2021, 9:42 AM"
    print(formatted)
}
Formatting Dates - More Examples swift · at 18:36 ↗
func formattingDatesMoreExamples() {
    // Note: This will use your current date & time plus current locale. Example output is for en_US locale.
    let date = Date.now

    let formatted = date.formatted(.dateTime.year().day().month())
    // example: "Jun 7, 2021"
    print(formatted)

    let formattedWide = date.formatted(.dateTime.year().day().month(.wide))
    // example: "June 7, 2021"
    print(formattedWide)

    let formattedWeekday = date.formatted(.dateTime.weekday(.wide))
    // example: "Monday"
    print(formattedWeekday)

    let logFormat = date.formatted(.iso8601)
    // example: "20210607T164200Z"
    print(logFormat)

    let fileNameFormat = date.formatted(.iso8601.year().month().day().dateSeparator(.dash))
    // example: "2021-06-07"
    print(fileNameFormat)
}
Formatting Intervals swift · at 20:30 ↗
func formattingIntervals() {
    // Note: This will use your current date & time plus current locale. Example output is for en_US locale.
    let now = Date.now
    // Note on time calculations: This represents the absolute point in time 5000 seconds from now. For calculations that are in terms of hours, days, etc., please use Calendar API.
    let later = now + TimeInterval(5000)

    let range = (now..<later).formatted()
    // example: "6/7/21, 9:42 – 11:05 AM"
    print(range)

    let noDate = (now..<later).formatted(date: .omitted, time: .complete)
    // example: "9:42:00 AM PDT – 11:05:20 AM PDT"
    print(noDate)

    let timeDuration = (now..<later).formatted(.timeDuration)
    // example: "1:23:20"
    print(timeDuration)

    let components = (now..<later).formatted(.components(style: .wide))
    // example: "1 hour, 23 minutes, 20 seconds"
    print(components)

    let relative = later.formatted(.relative(presentation: .named, unitsStyle: .wide))
    // example: "in 1 hour"
    print(relative)
}
Demo - SwiftUI and AttributedString swift · at 21:39 ↗
import SwiftUI

struct ContentView: View {
    @State var date = Date.now
    @Environment(\.locale) var locale

    var dateString : AttributedString {
        var str = date.formatted(.dateTime
                                    .minute()
                                    .hour()
                                    .weekday()
                                    .locale(locale)
                                    .attributed)

        let weekday = AttributeContainer
            .dateField(.weekday)

        let color = AttributeContainer
            .foregroundColor(.orange)

        str.replaceAttributes(weekday, with: color)

        return str
    }


    var body: some View {
        VStack {
            Text("Next free coffee")
            Text(dateString).font(.title2)
        }
        .multilineTextAlignment(.center)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environment(\.locale, Locale(identifier: "en_US"))
        ContentView()
            .environment(\.locale, Locale(identifier: "he_IL"))
        ContentView()
            .environment(\.locale, Locale(identifier: "es_ES"))
    }
}
Parsing Dates swift · at 23:53 ↗
func parsingDates() {
    let date = Date.now

    let format = Date.FormatStyle().year().day().month()
    let formatted = date.formatted(format)
    // example: "Jun 7, 2021"
    print(formatted)

    if let date = try? Date(formatted, strategy: format) {
        // example: 2021-06-07 07:00:00 +0000
        print(date)
    }
}
Parsing Dates - Strategies swift · at 24:23 ↗
func parsingDatesStrategies() {
    let strategy = Date.ParseStrategy(
        format: "\(year: .defaultDigits)-\(month: .twoDigits)-\(day: .twoDigits)",
        timeZone: TimeZone.current)

    if let date = try? Date("2021-06-07", strategy: strategy) {
        // date is 2021-06-07 07:00:00 +0000
        print(date)
    }
}
Formatting Numbers swift · at 25:30 ↗
func formattingNumbers() {
    // Note: This will use your current locale. Example output is for en_US locale.
    let value = 12345

    let formatted = value.formatted()
    // formatted is "12,345"
    print(formatted)
}
Formatting Numbers With Styles swift · at 25:36 ↗
func formattingNumbersWithStyles() {
    // Note: This will use your current locale. Example output is for en_US locale.
    let percent = 25
    let percentFormatted = percent.formatted(.percent)
    // percentFormatted is "25%"
    print(percentFormatted)

    let scientific = 42e9
    let scientificFormatted = scientific.formatted(.number.notation(.scientific))
    // scientificFormatted is "4.2E10"
    print(scientificFormatted)

    let price = 29
    let priceFormatted = price.formatted(.currency(code: "usd"))
    // priceFormatted is "$29.00"
    print(priceFormatted)
}
Formatting Lists swift · at 25:47 ↗
func formattingLists() {
    // Note: This will use your current locale. Example output is for en_US locale.
    let list = [25, 50, 75].formatted(.list(memberStyle: .percent, type: .or))
    // list is "25%, 50%, or 75%"
    print(list)
}
Receipt Tip View swift · at 26:05 ↗
struct ReceiptTipView: View {
    @State var tip = 0.15

    var body: some View {
        HStack {
            Text("Tip")
            Spacer()
            TextField("Amount",
                      value: $tip,
                      format: .percent)
        }
    }
}
Automatic Grammar Agreement swift · at 29:41 ↗
func addToOrderEnglish() {
    // Note: This will use your current locale. Example output is for en_US locale.
    let quantity = 2
    let size = "large"
    let food = "salad"

    let message = AttributedString(localized: "Add ^[\(quantity) \(size) \(food)](inflect: true) to your order")
    print(message)
}

Resources