Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 案例:创建通用的图表组件库

案例:创建通用的图表组件库

目标

构建一个可复用的 SwiftUI 图表组件库,支持多种图表类型和高度自定义配置,适用于不同项目需求。

实现步骤

1. 定义基础协议

protocol ChartDataPoint: Identifiable {
    var id: UUID { get }
    var value: Double { get }
    var label: String? { get }
}

protocol ChartStyle {
    var colors: [Color] { get }
    var lineWidth: CGFloat { get }
    var showAxis: Bool { get }
    // 其他样式属性...
}

2. 创建通用 ChartContainer 视图

struct ChartContainer<Content: View>: View {
    let title: String
    let style: ChartStyle
    @ViewBuilder let content: () -> Content
    
    var body: some View {
        VStack(alignment: .leading) {
            Text(title)
                .font(.headline)
            
            content()
                .chartStyle(style) // 自定义环境值
                .padding()
                .background(
                    RoundedRectangle(cornerRadius: 12)
                        .fill(Color(.systemBackground))
                )
        }
    }
}

3. 实现具体图表组件

可配置的柱状图组件

struct BarChartView<Data: RandomAccessCollection>: View where Data.Element: ChartDataPoint {
    let data: Data
    let style: BarChartStyle
    
    var body: some View {
        GeometryReader { geometry in
            HStack(alignment: .bottom, spacing: 2) {
                ForEach(data) { item in
                    VStack {
                        Rectangle()
                            .fill(style.color(for: item))
                            .frame(height: CGFloat(item.value) / maxValue * geometry.size.height)
                        
                        if style.showLabels {
                            Text(item.label ?? "")
                                .font(.caption)
                                .rotationEffect(.degrees(-45))
                        }
                    }
                }
            }
        }
    }
    
    private var maxValue: Double {
        data.map { $0.value }.max() ?? 1
    }
}

4. 使用 ViewBuilder 实现灵活组合

struct CompositeChartView<Primary: View, Secondary: View>: View {
    @ViewBuilder let primaryChart: () -> Primary
    @ViewBuilder let secondaryChart: () -> Secondary
    
    var body: some View {
        VStack {
            primaryChart()
            secondaryChart()
        }
    }
}

5. 通过 PreferenceKey 实现智能布局

struct ChartSizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

struct SizeReportingChart<Content: View>: View {
    @ViewBuilder let content: () -> Content
    
    var body: some View {
        content()
            .background(
                GeometryReader { proxy in
                    Color.clear
                        .preference(key: ChartSizePreferenceKey.self, value: proxy.size)
                }
            )
    }
}

使用示例

struct FinancialDashboard: View {
    let revenueData: [MonthlyData]
    let expenseData: [MonthlyData]
    
    var body: some View {
        ChartContainer(title: "2023 Financials") {
            CompositeChartView {
                BarChartView(data: revenueData, style: .revenueStyle)
                    .frame(height: 200)
            } secondaryChart: {
                LineChartView(data: expenseData, style: .expenseStyle)
                    .frame(height: 150)
            }
        }
    }
}

最佳实践

  1. 命名规范:

    • 使用 Chart 后缀命名所有图表组件(如 BarChart)
    • 样式类型使用 Style 后缀(如 BarChartStyle)
  2. 文档注释:

    /// 通用的柱状图组件
    /// - Parameters:
    ///   - data: 遵循 ChartDataPoint 协议的数据集合
    ///   - style: 柱状图样式配置
    /// - Note: 自动根据容器高度计算比例
    struct BarChartView: View { ... }
    
  3. 版本控制:

    • 使用语义化版本(SemVer)管理组件库
    • 提供迁移指南应对重大变更
  4. 测试覆盖:

    class ChartComponentsTests: XCTestCase {
        func testBarChartRendering() {
            let testData = [...]
            let view = BarChartView(data: testData, style: .default)
            assertSnapshot(of: view, as: .image)
        }
    }
    

扩展建议

  1. 添加主题系统支持暗黑模式
  2. 实现图表导出为图片/PDF功能
  3. 开发交互式预览组件
  4. 创建Playground实时编辑功能
Last Updated:: 5/18/25, 10:44 AM