# SwiftUI Custom Component Library: Tasarım Sistemi Oluşturma
Tutarlı bir UI, profesyonel uygulamaların ayırt edici özelliğidir. Apple, Google, Airbnb — hepsi kendi tasarım sistemlerine sahip. Bu rehberde SwiftUI ile production-grade bir component library oluşturacağız: design tokens'dan tema sistemine, custom modifier'lardan SPM ile dağıtıma kadar.
İçindekiler
- Design Tokens
- Tema Sistemi
- Typography System
- Button Components
- Input Components
- Card Components
- ViewModifier Patterns
- Animasyon Sistemi
- SPM Dağıtım
- Documentation & Preview
1. Design Tokens
Design token'lar, tasarım kararlarınızın tek kaynağıdır (single source of truth).
swift
1// Sources/DesignSystem/Tokens/DSSpacing.swift2public enum DSSpacing {3 /// 4pt4 public static let xxs: CGFloat = 45 /// 8pt6 public static let xs: CGFloat = 87 /// 12pt8 public static let sm: CGFloat = 129 /// 16pt10 public static let md: CGFloat = 1611 /// 24pt12 public static let lg: CGFloat = 2413 /// 32pt14 public static let xl: CGFloat = 3215 /// 48pt16 public static let xxl: CGFloat = 4817 /// 64pt18 public static let xxxl: CGFloat = 6419}20 21public enum DSRadius {22 public static let sm: CGFloat = 823 public static let md: CGFloat = 1224 public static let lg: CGFloat = 1625 public static let xl: CGFloat = 2426 public static let full: CGFloat = 999927}28 29public enum DSShadow {30 public struct Token {31 let color: Color32 let radius: CGFloat33 let x: CGFloat34 let y: CGFloat35 }36 37 public static let sm = Token(color: .black.opacity(0.05), radius: 2, x: 0, y: 1)38 public static let md = Token(color: .black.opacity(0.1), radius: 8, x: 0, y: 4)39 public static let lg = Token(color: .black.opacity(0.15), radius: 16, x: 0, y: 8)40 public static let xl = Token(color: .black.opacity(0.2), radius: 24, x: 0, y: 12)41}Token Karşılaştırma
Token Katmanı | Örnek | Açıklama |
|---|---|---|
**Primitive** | blue-500: #3B82F6 | Ham değerler |
**Semantic** | primary: blue-500 | Anlam taşıyan |
**Component** | button.bg: primary | Bileşene özel |
2. Tema Sistemi
swift
1// Sources/DesignSystem/Theme/DSTheme.swift2public protocol DSTheme {3 var colors: DSColorPalette { get }4 var typography: DSTypography { get }5 var spacing: DSSpacing.Type { get }6}7 8public struct DSColorPalette {9 public let primary: Color10 public let primaryVariant: Color11 public let secondary: Color12 public let background: Color13 public let surface: Color14 public let error: Color15 public let onPrimary: Color16 public let onBackground: Color17 public let onSurface: Color18 public let border: Color19 20 public init(21 primary: Color, primaryVariant: Color, secondary: Color,22 background: Color, surface: Color, error: Color,23 onPrimary: Color, onBackground: Color, onSurface: Color,24 border: Color25 ) {26 self.primary = primary27 self.primaryVariant = primaryVariant28 self.secondary = secondary29 self.background = background30 self.surface = surface31 self.error = error32 self.onPrimary = onPrimary33 self.onBackground = onBackground34 self.onSurface = onSurface35 self.border = border36 }37}38 39// Light tema40public struct LightTheme: DSTheme {41 public let colors = DSColorPalette(42 primary: Color(hex: "3B82F6"),43 primaryVariant: Color(hex: "2563EB"),44 secondary: Color(hex: "8B5CF6"),45 background: Color(hex: "FFFFFF"),46 surface: Color(hex: "F8FAFC"),47 error: Color(hex: "EF4444"),48 onPrimary: .white,49 onBackground: Color(hex: "0F172A"),50 onSurface: Color(hex: "334155"),51 border: Color(hex: "E2E8F0")52 )53 public let typography = DSTypography()54 public let spacing = DSSpacing.self55 56 public init() {}57}58 59// Dark tema60public struct DarkTheme: DSTheme {61 public let colors = DSColorPalette(62 primary: Color(hex: "60A5FA"),63 primaryVariant: Color(hex: "93C5FD"),64 secondary: Color(hex: "A78BFA"),65 background: Color(hex: "0F172A"),66 surface: Color(hex: "1E293B"),67 error: Color(hex: "F87171"),68 onPrimary: Color(hex: "0F172A"),69 onBackground: Color(hex: "F1F5F9"),70 onSurface: Color(hex: "CBD5E1"),71 border: Color(hex: "334155")72 )73 public let typography = DSTypography()74 public let spacing = DSSpacing.self75 76 public init() {}77}78 79// Environment key80private struct ThemeKey: EnvironmentKey {81 static let defaultValue: any DSTheme = LightTheme()82}83 84extension EnvironmentValues {85 public var dsTheme: any DSTheme {86 get { self[ThemeKey.self] }87 set { self[ThemeKey.self] = newValue }88 }89}Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
3. Typography System
swift
1public struct DSTypography {2 public enum Style {3 case largeTitle // 34pt Bold4 case title1 // 28pt Bold5 case title2 // 22pt Bold6 case title3 // 20pt Semibold7 case headline // 17pt Semibold8 case body // 17pt Regular9 case callout // 16pt Regular10 case subheadline // 15pt Regular11 case footnote // 13pt Regular12 case caption // 12pt Regular13 14 var font: Font {15 switch self {16 case .largeTitle: return .system(size: 34, weight: .bold, design: .rounded)17 case .title1: return .system(size: 28, weight: .bold, design: .rounded)18 case .title2: return .system(size: 22, weight: .bold)19 case .title3: return .system(size: 20, weight: .semibold)20 case .headline: return .system(size: 17, weight: .semibold)21 case .body: return .system(size: 17, weight: .regular)22 case .callout: return .system(size: 16, weight: .regular)23 case .subheadline: return .system(size: 15, weight: .regular)24 case .footnote: return .system(size: 13, weight: .regular)25 case .caption: return .system(size: 12, weight: .regular)26 }27 }28 }29}30 31// ViewModifier olarak32struct DSText: ViewModifier {33 let style: DSTypography.Style34 35 func body(content: Content) -> some View {36 content.font(style.font)37 }38}39 40extension View {41 public func dsTypography(_ style: DSTypography.Style) -> some View {42 modifier(DSText(style: style))43 }44}4. Button Components
swift
1public struct DSButton: View {2 public enum Style { case primary, secondary, outline, ghost, destructive }3 public enum Size { case sm, md, lg }4 5 let title: String6 let style: Style7 let size: Size8 let icon: String?9 let isLoading: Bool10 let action: () -> Void11 12 public init(13 _ title: String, style: Style = .primary, size: Size = .md,14 icon: String? = nil, isLoading: Bool = false, action: @escaping() -> Void15 ) {16 self.title = title; self.style = style; self.size = size17 self.icon = icon; self.isLoading = isLoading; self.action = action18 }19 20 @Environment(\.dsTheme) private var theme21 22 public var body: some View {23 Button(action: action) {24 HStack(spacing: DSSpacing.xs) {25 if isLoading {26 ProgressView()27 .progressViewStyle(.circular)28 .tint(foregroundColor)29 } else {30 if let icon {31 Image(systemName: icon)32 }33 Text(title)34 .font(fontSize)35 .fontWeight(.semibold)36 }37 }38 .frame(maxWidth: style == .ghost ? nil : .infinity)39 .padding(.horizontal, horizontalPadding)40 .padding(.vertical, verticalPadding)41 .background(backgroundColor)42 .foregroundStyle(foregroundColor)43 .clipShape(RoundedRectangle(cornerRadius: DSRadius.md))44 .overlay(45 RoundedRectangle(cornerRadius: DSRadius.md)46 .stroke(borderColor, lineWidth: style == .outline ? 1.5 : 0)47 )48 }49 .disabled(isLoading)50 .opacity(isLoading ? 0.8 : 1)51 }52 53 private var backgroundColor: Color {54 switch style {55 case .primary: return theme.colors.primary56 case .secondary: return theme.colors.secondary57 case .outline, .ghost: return .clear58 case .destructive: return theme.colors.error59 }60 }61 62 private var foregroundColor: Color {63 switch style {64 case .primary: return theme.colors.onPrimary65 case .secondary: return .white66 case .outline: return theme.colors.primary67 case .ghost: return theme.colors.onSurface68 case .destructive: return .white69 }70 }71 72 private var borderColor: Color {73 style == .outline ? theme.colors.primary : .clear74 }75 76 private var fontSize: Font {77 switch size {78 case .sm: return .system(size: 14, weight: .semibold)79 case .md: return .system(size: 16, weight: .semibold)80 case .lg: return .system(size: 18, weight: .semibold)81 }82 }83 84 private var horizontalPadding: CGFloat {85 switch size { case .sm: return 12; case .md: return 16; case .lg: return 24 }86 }87 88 private var verticalPadding: CGFloat {89 switch size { case .sm: return 8; case .md: return 12; case .lg: return 16 }90 }91}5. Input Components
swift
1public struct DSTextField: View {2 let label: String3 @Binding var text: String4 let placeholder: String5 let error: String?6 let icon: String?7 8 @FocusState private var isFocused: Bool9 @Environment(\.dsTheme) private var theme10 11 public init(12 _ label: String, text: Binding<String>,13 placeholder: String = "", error: String? = nil, icon: String? = nil14 ) {15 self.label = label; self._text = text16 self.placeholder = placeholder; self.error = error; self.icon = icon17 }18 19 public var body: some View {20 VStack(alignment: .leading, spacing: DSSpacing.xs) {21 Text(label)22 .font(.system(size: 14, weight: .medium))23 .foregroundStyle(theme.colors.onSurface)24 25 HStack(spacing: DSSpacing.xs) {26 if let icon {27 Image(systemName: icon)28 .foregroundStyle(isFocused ? theme.colors.primary : theme.colors.onSurface.opacity(0.5))29 }30 TextField(placeholder, text: $text)31 .focused($isFocused)32 }33 .padding(.horizontal, DSSpacing.md)34 .padding(.vertical, DSSpacing.sm)35 .background(theme.colors.surface)36 .clipShape(RoundedRectangle(cornerRadius: DSRadius.md))37 .overlay(38 RoundedRectangle(cornerRadius: DSRadius.md)39 .stroke(borderColor, lineWidth: isFocused ? 2 : 1)40 )41 42 if let error {43 Text(error)44 .font(.system(size: 12))45 .foregroundStyle(theme.colors.error)46 }47 }48 }49 50 private var borderColor: Color {51 if error != nil { return theme.colors.error }52 if isFocused { return theme.colors.primary }53 return theme.colors.border54 }55}6. Card Components
swift
1public struct DSCard<Content: View>: View {2 let content: Content3 let padding: CGFloat4 5 @Environment(\.dsTheme) private var theme6 7 public init(padding: CGFloat = DSSpacing.md, @ViewBuilder content: () -> Content) {8 self.padding = padding9 self.content = content()10 }11 12 public var body: some View {13 content14 .padding(padding)15 .background(theme.colors.surface)16 .clipShape(RoundedRectangle(cornerRadius: DSRadius.lg))17 .shadow(18 color: DSShadow.md.color,19 radius: DSShadow.md.radius,20 x: DSShadow.md.x,21 y: DSShadow.md.y22 )23 }24}7. ViewModifier Patterns
swift
1// Shimmer loading effect2public struct ShimmerModifier: ViewModifier {3 @State private var phase: CGFloat = 04 let isActive: Bool5 6 public func body(content: Content) -> some View {7 if isActive {8 content9 .redacted(reason: .placeholder)10 .overlay(11 GeometryReader { geo in12 LinearGradient(13 colors: [.clear, .white.opacity(0.4), .clear],14 startPoint: .leading, endPoint: .trailing15 )16 .frame(width: geo.size.width * 0.6)17 .offset(x: phase * geo.size.width * 1.6 - geo.size.width * 0.3)18 }19 .clipped()20 )21 .onAppear {22 withAnimation(.linear(duration: 1.5).repeatForever(autoreverses: false)) {23 phase = 124 }25 }26 } else {27 content28 }29 }30}31 32extension View {33 public func shimmer(isActive: Bool = true) -> some View {34 modifier(ShimmerModifier(isActive: isActive))35 }36}8. Animasyon Sistemi
swift
1public enum DSAnimation {2 public static let springSnappy = Animation.spring(response: 0.3, dampingFraction: 0.7)3 public static let springSmooth = Animation.spring(response: 0.5, dampingFraction: 0.8)4 public static let springBouncy = Animation.spring(response: 0.4, dampingFraction: 0.6)5 public static let easeOut = Animation.easeOut(duration: 0.25)6 public static let easeInOut = Animation.easeInOut(duration: 0.3)7}8 9// Kullanım10Button("Kaydet") { }11 .scaleEffect(isPressed ? 0.95 : 1)12 .animation(DSAnimation.springSnappy, value: isPressed)9. SPM ile Dağıtım
swift
1// Package.swift2// swift-tools-version: 5.93import PackageDescription4 5let package = Package(6 name: "AppDesignSystem",7 platforms: [.iOS(.v16), .macOS(.v13)],8 products: [9 .library(name: "AppDesignSystem", targets: ["AppDesignSystem"]),10 ],11 targets: [12 .target(13 name: "AppDesignSystem",14 resources: [.process("Resources")]15 ),16 .testTarget(17 name: "AppDesignSystemTests",18 dependencies: ["AppDesignSystem"]19 ),20 ]21)10. Preview Catalog
swift
1#Preview("Buttons") {2 VStack(spacing: 16) {3 DSButton("Primary", style: .primary) { }4 DSButton("Secondary", style: .secondary) { }5 DSButton("Outline", style: .outline) { }6 DSButton("Ghost", style: .ghost, icon: "plus") { }7 DSButton("Loading", isLoading: true) { }8 DSButton("Destructive", style: .destructive, icon: "trash") { }9 }10 .padding()11 .environment(\.dsTheme, LightTheme())12}ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:

