A novel way to set attributes to the Font in SwiftUI.

Overview

AttributedFont

Build

SwiftUI에서 Font에 어트리뷰트를 설정하는 획기적인 방법.

import SwiftUI
import AttributedFont

let font = AttributedFont
    .custom(
        "Noto Sans KR",
        fixedSize: 17,
        attributes: .init(
              kerning: -0.2,
              lineHeightMultiple: 1.5
        )
    )

목차

동기

타이포그래피 시스템을 정의할 때, 자간과 줄 높이를 폰트 기본값 대신 사용자 설정 값으로 직접 조정해 사용하는 경우가 종종 있습니다. 산타의 타이포그래피 시스템에서도 Spoqa Han Sans를 자간과 줄 높이를 조정해 사용하고 있는데요. SwiftUI에서는 자간 조정을 위해 kerning(_:), tracking(_:) 등의 모디파이어를 제공하지만, 이 모디파이어들은 Text 뷰에만 적용이 가능하다는 한계가 있습니다. 다시 말해, VStack이나 ButtonStyleConfiguration.Label 같이 Text가 아닌 뷰에는 커닝과 트래킹 값을 적용할 수 없습니다.

이 같은 한계는 @Environment를 사용해 값을 전달하는 font(_:) 모디파이어와 다르게, kerning(_:)tracking(_:) 모디파이어의 경우 Text 인스턴스를 직접 수정하는 방식으로 작동하기 때문에 발생하는데요. 반대로 생각해 본다면, 커닝과 트래킹 값도 @Environment를 통해 전달할 수 있게 된다면 앞에서 말씀드렸던 문제를 해결할 수 있다는 결론에 이르게 됩니다.

AttributedFont는 SwiftUI의 Font를 확장하여 kerning, tracking 등의 어트리뷰트를 @Environment를 통해 전달함으로써 Text가 아닌 뷰에도 커닝과 트래킹 값을 적용할 수 있도록 합니다. 여기에 더해, lineHeightMultiple 어트리뷰트를 지원함으로써 SwiftUI의 lineSpacing(_:) 모디파이어로는 완전히 달성할 수 없었던 줄 높이 값 변경이 가능하록 도와줍니다.

기능

  • 폰트 인스턴스에 커닝과 트래킹 값을 지정할 수 있습니다.
  • 폰트 인스턴스에 줄 높이 배수 값을 지정할 수 있습니다.

사용

폰트 생성하기

Font 인스턴스를 생성하는 것과 유사하게, AttributedFontcustom(_:fixedSize:attributes:) 메소드를 사용하여 인스턴스를 생성합니다.

AttributedFont.custom("Noto Sans KR", fixedSize: 17, attributes: nil)

attributes 파라미터를 통해 폰트 인스턴스에 어트리뷰트를 설정할 수 있습니다. 현재 AttributedFont.Attributeskerning, tracking, lineHeightMultiple의 세 가지 값을 지원합니다.

AttributedFont
    .custom(
        "Noto Sans KR",
        fixedSize: 17,
        attributes: .init(
            kerning: -0.2,
            lineHeightMultiple: 1.5
        )
    )

trackingkerning 값을 동시에 지정할 경우, SwiftUI에서의 기본 동작과 마찬가지로 tracking 값만 적용되며 kerning 값은 무시됩니다.

AttributedFont
    .custom(
        "Noto Sans KR",
        fixedSize: 17,
        attributes: .init(
            kerning: 0, // tracking과 함께 지정된 kerning 값은 무시됩니다.
            tracking: -0.2,
            lineHeightMultiple: 1.5
        )
    )

폰트 적용하기

AttributedFont를 사용하기 위해서는 AttributedText를 함께 사용해야 합니다. AttributedTextAttributedFont@Environment로 받을 수 있도록 Text를 확장시킨 View입니다.

AttributedText 또는 AttributedText를 임베딩하고 있는 뷰에 attributedFont(_:) 모디파이어를 사용하여 폰트 어트리뷰트를 적용합니다. AttributedFontAttributedText에만 적용되어 보여집니다.

AttributedText("Hello, world!")
    .attributedFont(
        .custom(
            "Noto Sans KR",
            fixedSize: 17,
            attributes: .init(
                kerning: -0.2,
                lineHeightMultiple: 1.5
            )
        )
    )

자주 사용하는 폰트 인스턴스의 경우, AttributedFontextension으로 정적 프로퍼티를 정의하면 간편하게 사용할 수 있습니다.

extension AttributedFont {
    static let body: Self = .custom(
        "Noto Sans KR",
        fixedSize: 17,
        attributes: .init(
            kerning: -0.2,
            lineHeightMultiple: 1.5
        )
    )
}

AttributedText("Hello, world!")
    .attributedFont(.body)

텍스트 조작하기

AttributedFontAttributedTextFontText에서 사용 가능한 대부분의 모디파이어와 연산자를 지원합니다.

AttributedFont
struct AttributedFont: Equatable, Hashable {

    // Creating Custom Fonts
    static func custom(_ name: String, fixedSize: CGFloat, attributes: Attributes) -> Self

    // Styling a Font
    func italic() -> Self
    func smallCaps() -> Self
    func lowercaseSmallCaps() -> Self
    func uppercaseSmallCaps() -> Self
    func monospacedDigit() -> Self
    func weight(_ weight: Font.Weight) -> Self
    func bold() -> Self
    func monospaced() -> Self
    func leading(_ leading: Font.Leading) -> Self
}
AttributedText
struct AttributedText: View, Equatable {

    // Creating a Text View from a String
    init(_ key: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil)
    @inlinable init(verbatim content: String)
    init<S: StringProtocol>(_ content: S)

    // Creating a Text View from an Attributed String
    init(_ attributedContent: AttributedString)

    // Creating a Text View for a Date
    init(_ dates: ClosedRange)
    init(_ interval: DateInterval)
    init(_ date: Date, style: Text.DateStyle)

    // Creating a Text View with Formatting
    init<F: FormatStyle>(_ input: F.FormatInput, format: F) where F.FormatInput: Equatable, F.FormatOutput == String
    init<Subject: ReferenceConvertible>(_ subject: Subject, formatter: Formatter)
    init<Subject: NSObject>(_ subject: Subject, formatter: Formatter)

    // Creating a Text View from an Image
    init(_ image: Image)

    // Choosing a Font
    func attributedFont(_ attributedFont: AttributedFont?) -> Self
    func font(_ font: Font?) -> Self
    func fontWeight(_ weight: Font.Weight?) -> Self

    // Styling the View’s Text
    func foregroundColor(_ color: Color?) -> Self
    func bold() -> Self
    func italic() -> Self
    func monospacedDigit() -> Self
    func strikethrough(_ active: Bool = true, color: Color? = nil) -> Self
    func underline(_ active: Bool = true, color: Color? = nil) -> Self
    func kerning(_ kerning: CGFloat) -> Self
    func tracking(_ tracking: CGFloat) -> Self
    func baselineOffset(_ baselineOffset: CGFloat) -> Self

    // Configuring VoiceOver
    func speechAlwaysIncludesPunctuation(_ value: Bool = true) -> Self
    func speechSpellsOutCharacters(_ value: Bool = true) -> Self
    func speechAdjustedPitch(_ value: Double) -> Self
    func speechAnnouncementsQueued(_ value: Bool = true) -> Self

    // Providing Accessibility Information
    func accessibilityTextContentType(_ value: AccessibilityTextContentType) -> Self
    func accessibilityHeading(_ level: AccessibilityHeadingLevel) -> Self
    func accessibilityLabel(_ label: Text) -> Self
    func accessibilityLabel(_ labelKey: LocalizedStringKey) -> Self
    func accessibilityLabel<S: StringProtocol>(_ label: S) -> Self

    // Combining Text Views
    static func concatenate(_ views: [AttributedText], attributedFont: AttributedFont) -> Self
    static func concatenate(_ views: AttributedText..., attributedFont: AttributedFont) -> Self
}

텍스트 결합하기

Text+ 연산을 지원하기 위해, AttributedText에서도 concatenate(_:attributedFont:) 메소드를 제공하여 텍스트 결합을 지원합니다.

AttributedText.concatenate(
    AttributedText("Hello,\n").attributedFont(.subheadline).foregroundColor(.secondary),
    AttributedText("world!").attributedFont(.headline).foregroundColor(.primary),
    attributedFont: .headline
)

lineSpacing(_:)과 상하단 padding(_:_:)을 사용하여 lineHeightMultiple을 적용하는 AttributedText의 특성으로 인해, 여러 개의 AttributedText를 결합할 때에는 개별적으로 설정된 lineHeightMultiple 값이 무시되며, concatenate(_:attributedFont:) 메소드의 마지막 파라미터로 전달된 attributedFontlineHeightMultiple 값을 전체 적용합니다.

요구 사항

  • Swift 5.3+
  • Xcode 12.0+
  • iOS 14.0+
  • Mac Catalyst 14.0+
  • macOS 11.0+
  • tvOS 14.0+
  • watchOS 7.0+

설치

Swift Package Manager

Package.swift 파일의 dependencies에 아래 라인을 추가합니다.

.package(url: "https://github.com/riiid/AttributedFont.git", .upToNextMajor(from: "1.0.0"))

그 다음, AttributedFont를 타겟의 의존성으로 추가합니다.

.target(name: "MyTarget", dependencies: ["AttributedFont"])

완성된 디스크립션은 아래와 같습니다.

// swift-tools-version:5.1

import PackageDescription

let package = Package(
    name: "MyPackage",
    dependencies: [
        .package(url: "https://github.com/riiid/AttributedFont.git", .upToNextMajor(from: "1.0.0"))
    ],
    targets: [
        .target(name: "MyTarget", dependencies: ["AttributedFont"])
    ]
)

Xcode

File > Swift Packages > Add Package Dependency를 선택한 다음, 아래의 URL을 입력합니다.

https://github.com/riiid/AttributedFont.git

자세한 내용은 Adding Package Dependencies to Your App을 참조하세요.

기여하기

저희는 모든 종류의 기여를 환영하며, 기여해 주시는 분들의 모든 의견을 존중합니다. 간단한 기능 추가, 버그 픽스, 오타 수정 등이라도 주저하지 말고 이슈PR을 생성하여 의견을 제기해 주세요.

메인테이너

도움을 주신 분들

  • 이민호 (@tisohjung): 초기 버전에서 lineHeightMultiple이 제대로 적용되지 않는 문제를 발견하고 수정해주셨습니다.

라이선스

AttributedFont는 MIT 라이선스 하에 배포됩니다. 자세한 내용은 LICENSE를 참조하세요.

You might also like...
A powerful, beautiful way to experience Formula1

F1 Pocket Companion A powerful, beautiful way to experience Formula1, right on your iPhone Note This project will probably change it's name. I'm curre

an onboarding app built in SwiftUI
an onboarding app built in SwiftUI

hello hello is application onboarding macOS devices. Inspired by Kandji's Liftoff Logo created with Type with Pride font family Warning DO NOT USE THI

BioViewer - Protein (.pdb, .cif and .fasta) viewer for iPhone, iPad and Mac, using SwiftUI + SceneKit
BioViewer - Protein (.pdb, .cif and .fasta) viewer for iPhone, iPad and Mac, using SwiftUI + SceneKit

BioViewer - Protein (.pdb, .cif and .fasta) viewer for iPhone, iPad and Mac, using SwiftUI + SceneKit

Create an easy to peek SwiftUI View to showcase your own data, catalog, images, or anything you'd like.
Create an easy to peek SwiftUI View to showcase your own data, catalog, images, or anything you'd like.

Create an easy to peek SwiftUI View to showcase your own data, catalog, images, or anything you'd like.

An Apple Watch remake of the Poketch from Pokemon Diamond and Pearl made with SwiftUI
An Apple Watch remake of the Poketch from Pokemon Diamond and Pearl made with SwiftUI

Apple Watch Poketch What is it? It's an Apple Watch remake of the "Poketch" from Pokemon Diamond and Pearl made with SwiftUI! Check out the YouTube vi

This is a fully functioning Guess The Flag game I created as part of my 100 days of SwiftUI course with Paul Hudson.
This is a fully functioning Guess The Flag game I created as part of my 100 days of SwiftUI course with Paul Hudson.

GuessTheFlag This is a fully functioning Guess The Flag game that was a part of my 100 days of SwiftUI course with Paul Hudson. In this app my challen

How to develop an iOS 14 application with SwiftUI 2.0 framework. How to create an Onboarding Screen with Page Tab View

Ama-Fruits USER INTERFACE AND USER EXPERIENCE APP DESIGN How to develop an iOS 14 application with SwiftUI 2.0 framework. How to create an Onboarding

 Home Assistant Native iOS Application built with SwiftUI for iOS 15+
Home Assistant Native iOS Application built with SwiftUI for iOS 15+

Home Assistant - Native iOS SwiftUI Application Screenshots Disclaimer - Please read This application is mostly a not-working mockup written in SwiftU

GraphQL based Jetpack Compose, Wear Compose and SwiftUI Kotlin Multiplatform sample
GraphQL based Jetpack Compose, Wear Compose and SwiftUI Kotlin Multiplatform sample

GraphQL based Jetpack Compose, Wear Compose and SwiftUI Kotlin Multiplatform sample

Releases(v1.0.1)
Owner
Riiid
On a mission to remove inefficiency, inconsistency and inequality in education through technology
Riiid
Puma - A set of build utilities to automate mobile application development and deployment

Puma → https://github.com/onmyway133/Swiftlane Puma is a set of build utilities

Puma Swift 5 Oct 8, 2022
SafeDecoder - a swift package that set defaults when Codable fails to decode a field

SafeDecoder is a swift package that set defaults when Codable fails to decode a field. SafeDecoder supports configurable default values, See SafeDecoder.Configuration.

GodL 4 Mar 21, 2022
Set `Open using Rosetta` option on Xcode easily

xcode-arch A utility to switch running architecture of Xcode on M1 mac. Motivation Currently, there is no way to toggle Open using Rosetta option othe

Takuhiro Muta 7 Aug 8, 2022
Wasmic allows you to run WebAssembly in a safe way on iOS.

wasmic-ios Bootstrap $ git clone https://github.com/kateinoigakukun/wasmic-ios.git $ git -c submodule."fastlane".update=none submodule update --init -

Yuta Saito 58 Dec 12, 2022
🌳 Environment – a nicer, type-safe way of working with environment variables in Swift.

?? Environment Welcome to Environment – a nicer, type-safe way of working with environment variables in Swift. Usage Access Environment Variables The

Will Lisac 31 Dec 17, 2022
A Simple way help you drop or drag your source (like UIImage) between different App.

A Simple way help you drop or drag your source (like UIImage) between different App.

逸风 13 Nov 24, 2022
MicrofrontendGenerator - Script for creating micro frontends for Mobile in a simple and easy way

Introdução Template para a criação de SDK iOS. Existem duas opções de template:

Julio Fernandes Jr 4 Nov 2, 2022
ZakatFatoora - A simple way to implement e-invoicing (FATOORA) for iOS

ZakatFatoora - A simple way to implement e-invoicing (FATOORA) for iOS

Faisal H Almuhaidly 9 Dec 15, 2022
A custom calculator for deg to rad conversion & the other way round

Lilium Features A custom calculator for deg to rad conversion & the other way round. How to use Slide up the dock and you should see Lilium. An activa

null 2 Nov 20, 2022
Appwrite playground - a simple way to explore the Appwrite API & Appwrite Apple SDK

Appwrite's Apple Playground ?? Appwrite playground is a simple way to explore the Appwrite API & Appwrite Apple SDK. Use the source code of this repos

Appwrite 24 Nov 22, 2022