Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions Dori-iOS/DesignSystem/Typography/DesignTokenDemoView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//
// DesignTokenDemoView.swift
// Dori-iOS
//
// Created by 강동영 on 2/6/26.
//

import SwiftUI

fileprivate struct DesignTokenDemoView: View {
@State private var isColorSection = false

var body: some View {
ScrollView {
if isColorSection { colorSection }
else { typeSection }
}
}

var colorSection: some View {
VStack {
ForEach(Self.brandColors, id: \.self) { color in
VStack {
Rectangle()
.frame(height: 50)
.foregroundStyle(color)
}
}

ForEach(Self.greyColors, id: \.self) { color in
VStack {
Rectangle()
.frame(height: 50)
.foregroundStyle(color)
}
}
}
.padding()
}

var typeSection: some View {
ScrollView {
Section {
ForEach(Self.typoHeadings, id: \.self) { heading in
ZStack {
Rectangle()
.frame(height: 50)
.foregroundStyle(.grey100)
Text("도리 화이팅")
.pretendard(.heading(heading))
}
}
} header: {
Text("헤딩")
.padding()
.pretendard(.heading(.h15))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.background(.main200)
.padding()
}

Section {
ForEach(Self.typoSubTitles, id: \.self) { subtitle in
ZStack {
Rectangle()
.frame(height: 50)
.foregroundStyle(.grey200)
Text("도리 화이팅")
.pretendard(.subtitle(subtitle))
}
}
} header: {
Text("서브 타이틀")
.padding()
.pretendard(.heading(.h15))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.background(.main200)
.padding()
}

Section {
ForEach(Self.typoBodys, id: \.self) { body in
ZStack {
Rectangle()
.frame(height: 50)
.foregroundStyle(.grey300)
Text("도리 화이팅")
.pretendard(.body(body))
}
}
} header: {
Text("바디")
.padding()
.pretendard(.heading(.h15))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.background(.main200)
.padding()
}

Section {
ForEach(Self.typoCaptions, id: \.self) { caption in
ZStack {
Rectangle()
.frame(height: 50)
.foregroundStyle(.grey400)
Text("도리 화이팅")
.pretendard(.caption(caption))
}
}
} header: {
Text("캡션")
.padding()
.pretendard(.heading(.h15))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.background(.main200)
.padding()
}
}
}
}

extension DesignTokenDemoView {
private static let brandColors: [Color] = [
Color.main100,
Color.main200,
Color.main300
]

private static let greyColors: [Color] = [
Color.white,
Color.grey100,
Color.grey200,
Color.grey300,
Color.grey400,
Color.grey500,
Color.grey600,
Color.grey700,
Color.grey800,
Color.black
]

private static let typoHeadings: [TypoStyle.Heading] = TypoStyle.Heading.allCases
private static let typoSubTitles: [TypoStyle.SubTitle] = TypoStyle.SubTitle.allCases
private static let typoBodys: [TypoStyle.Body] = TypoStyle.Body.allCases
private static let typoCaptions: [TypoStyle.Caption] = TypoStyle.Caption.allCases
}

#Preview {
DesignTokenDemoView()
}
66 changes: 66 additions & 0 deletions Dori-iOS/DesignSystem/Typography/FontManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// FontManager.swift
// Dori-iOS
//
// Created by 강동영 on 2/6/26.
//

import Foundation
import CoreGraphics
import CoreText

@MainActor
public enum FontManager {
private static var registeredFonts: Set<String> = []

/// 폰트가 필요할 때마다 자동으로 등록하는 함수
public static func registerFontIfNeeded(_ fontName: String) {
// 이미 등록된 폰트는 스킵
guard !registeredFonts.contains(fontName) else { return }

// Bundle.module에서 폰트 파일 찾기
guard let fontURL = findFontURL(for: fontName) else {
print("⚠️ Font file not found for: \(fontName)")
return
}

var error: Unmanaged<CFError>?

// 폰트 등록
if CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, &error) {
registeredFonts.insert(fontName)
print("✅ Font registered: \(fontName)")
} else if let error = error?.takeRetainedValue() {
print("⚠️ Font registration failed for \(fontName): \(error)")
}
}

/// Bundle.module에서 폰트 파일 URL 찾기
private static func findFontURL(for fontName: String) -> URL? {
let extensions = FontExtension.allCases

for ext in extensions {
if let url = Bundle.main.url(forResource: fontName, withExtension: ext.rawValue) {
return url
}
}

print("⚠️ Font file not found for: \(fontName)")
return nil
}

/// 모든 Pretendard 폰트를 한번에 등록하는 함수 (옵션)
public static func registerAllFonts() {
let fontNames = [
"Pretendard-Regular",
"Pretendard-Medium",
"Pretendard-SemiBold",
]
fontNames.forEach { registerFontIfNeeded($0) }
}
}

enum FontExtension: String, CaseIterable {
case ttf
case otf
}
55 changes: 55 additions & 0 deletions Dori-iOS/DesignSystem/Typography/FontProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// FontProvider.swift
// Dori-iOS
//
// Created by 강동영 on 2/6/26.
//

import Foundation

// MARK: - Font Provider Protocol
public protocol FontProvider {
var regular: String { get }
var medium: String { get }
var semiBold: String { get }
var bold: String? { get }
}

// MARK: - Pretendard Font Provider
public struct PretendardProvider: FontProvider {
public let regular: String = FontName.regular.name
public let medium: String = FontName.medium.name
public let semiBold: String = FontName.semiBold.name
public let bold: String? = FontName.bold.name

public init() {}
}

extension PretendardProvider {
enum FontName {
case regular, medium, semiBold, bold

var name: String {
switch self {
case .regular: return "Pretendard-Regular"
case .medium: return "Pretendard-Medium"
case .semiBold: return "Pretendard-SemiBold"
case .bold: return "Pretendard-Bold"
}
}
}
}

// MARK: - Font Weight Enum
public enum FontWeight {
case regular, medium, semiBold, bold

public func getFontName(from provider: FontProvider) -> String {
switch self {
case .regular: return provider.regular
case .medium: return provider.medium
case .semiBold: return provider.semiBold
case .bold: return provider.bold ?? provider.semiBold
}
}
}
41 changes: 41 additions & 0 deletions Dori-iOS/DesignSystem/Typography/FontStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// FontStyle.swift
// Dori-iOS
//
// Created by 강동영 on 2/6/26.
//

import SwiftUI

// MARK: - Font 타입 속성을 정의하는 구조체
public enum FontType: Equatable {
case system(Font.Weight)
case custom(String)
}

// MARK: - TypoStyle 혹은 직접 활용도 가능하끔 구조 작성
@MainActor
public struct FontStyle {
public let font: Font
public let fontSize: CGFloat
public let lineHeight: CGFloat

public init(
_ family: FontType = .system(.regular),
size: CGFloat = 16,
lineHeight: CGFloat = 1.5,
) {
self.fontSize = size

switch family {
case .system(let weight):
self.font = .system(size: size, weight: weight)
case .custom(let fontName):
// Bundle.module에서 폰트 등록 후 사용
FontManager.registerFontIfNeeded(fontName)
self.font = .custom(fontName, size: size)
}

self.lineHeight = size * lineHeight - size
}
}
66 changes: 66 additions & 0 deletions Dori-iOS/DesignSystem/Typography/TypoStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// TypoStyle.swift
// Dori-iOS
//
// Created by 강동영 on 2/6/26.
//

import Foundation

@MainActor
public enum TypoStyle {
case heading(TypoStyle.Heading)
case subtitle(TypoStyle.SubTitle)
case body(TypoStyle.Body)
case caption(TypoStyle.Caption)

// MARK: - 폰트에 의존하지 않는 스타일 정의
private var styleSpec: (weight: FontWeight, size: CGFloat) {
switch self {
case .heading(let heading):
return (.bold, heading.size)

case .subtitle(let subtitle):
return (.semiBold, subtitle.size)

case .body(let body):
return (.medium, body.size)

case .caption(let caption):
return (.regular, caption.size)
}
}

// MARK: - FontProvider를 받아서 FontStyle 생성
public func getFontStyle(with provider: FontProvider) -> FontStyle {
let spec = styleSpec
let fontName = spec.weight.getFontName(from: provider)
return FontStyle(.custom(fontName), size: spec.size)
}
}

public extension TypoStyle {
enum Heading: Int, CaseIterable {
case h11 = 11, h13 = 13, h14 = 14, h15 = 15, h16 = 16, h20 = 20, h30 = 30

var size: CGFloat { CGFloat(rawValue) }
}

enum SubTitle: Int, CaseIterable {
case t12 = 12, t14 = 14, t15 = 15, t16 = 16

var size: CGFloat { CGFloat(rawValue) }
}

enum Body: Int, CaseIterable {
case b11 = 11, b12, b13, b14, b15, b16

var size: CGFloat { CGFloat(rawValue) }
}

enum Caption: Int, CaseIterable {
case c11 = 11, c12, c13, c14, c15, c16, c20 = 20

var size: CGFloat { CGFloat(rawValue) }
}
}
Loading