External Libraries Integration ja - Tai-Kimura/SwiftJsonUI GitHub Wiki

外部ライブラリの統合

このガイドでは、SPM ライブラリからのサードパーティ SwiftUI コンポーネントを、カスタムコンバーターを使用して SwiftJsonUI プロジェクトに統合する方法を説明します。

目次

概要

SwiftJsonUI では、外部ライブラリの SwiftUI コンポーネントをラップして、JSON レイアウトで使用できるようにすることができます。これは以下の手順で実現されます:

  1. SPM ライブラリをプロジェクトに追加
  2. sjui g converter でカスタムコンバーターを生成
  3. 生成された Swift ファイルを修正して外部コンポーネントを返す
  4. JSON レイアウトでコンポーネントを使用

ステップバイステップガイド

ステップ 1: 外部ライブラリを追加

Package.swift に外部ライブラリを追加:

dependencies: [
    .package(url: "https://github.com/Tai-Kimura/SwiftJsonUI.git", from: "7.1.1"),
    .package(url: "https://github.com/example/SwiftUICalendar.git", from: "1.0.0")
]

ステップ 2: カスタムコンバーターを生成

適切な属性でラッパーコンポーネントを生成:

# 例:Calendar コンポーネントのラッピング
sjui g converter Calendar --attributes "selectedDate:Date,@onDateSelected:String,showWeekNumbers:Bool"

# 例:Chart コンポーネントのラッピング
sjui g converter LineChart --attributes "dataPoints:[Double],lineColor:Color,showGrid:Bool"

ステップ 3: 生成された Swift ファイルを修正

生成された Swift ファイル(Extensions/Calendar.swift)を編集して外部コンポーネントを使用:

import SwiftUI
import SwiftUICalendar  // 外部ライブラリをインポート

struct Calendar: View {
    let selectedDate: Date
    @SwiftUI.Binding var onDateSelected: String?
    let showWeekNumbers: Bool
    
    init(
        selectedDate: Date,
        onDateSelected: SwiftUI.Binding<String?>,
        showWeekNumbers: Bool
    ) {
        self.selectedDate = selectedDate
        self._onDateSelected = onDateSelected
        self.showWeekNumbers = showWeekNumbers
    }
    
    var body: some View {
        // 外部ライブラリのコンポーネントを返す
        CalendarView(
            selectedDate: selectedDate,
            showWeekNumbers: showWeekNumbers,
            onDateSelected: { date in
                // 日付を文字列に変換してバインディングを更新
                let formatter = DateFormatter()
                formatter.dateStyle = .medium
                formatter.locale = Locale(identifier: "ja_JP")
                onDateSelected = formatter.string(from: date)
            }
        )
        .frame(height: 350)  // 必要なモディファイアを追加
    }
}

ステップ 4: JSON で使用

これで JSON レイアウトで外部コンポーネントを使用できます:

{
  "type": "Calendar",
  "selectedDate": "2025-08-27",
  "onDateSelected": "@{selectedDateString}",
  "showWeekNumbers": true
}

実例

例 1: チャートライブラリの統合

1. コンバーターを生成:

sjui g converter BarChart --attributes "data:[Double],barColor:Color,spacing:Double"

2. Extensions/BarChart.swift を修正:

import SwiftUI
import Charts  // 外部チャートライブラリ

struct BarChart: View {
    let data: [Double]
    let barColor: Color
    let spacing: Double
    
    var body: some View {
        // 外部ライブラリのチャートコンポーネントを使用
        ChartView(data: data)
            .barColor(barColor)
            .spacing(spacing)
            .frame(height: 200)
    }
}

3. JSON で使用:

{
  "type": "BarChart",
  "data": [10, 25, 15, 30, 20],
  "barColor": "#007AFF",
  "spacing": 4.0
}

例 2: ローディングインジケーターライブラリの統合

1. コンバーターを生成:

sjui g converter LoadingSpinner --attributes "@isLoading:Bool,size:Double,color:Color"

2. Extensions/LoadingSpinner.swift を修正:

import SwiftUI
import ActivityIndicatorView  // 外部ライブラリ

struct LoadingSpinner: View {
    @SwiftUI.Binding var isLoading: Bool
    let size: Double
    let color: Color
    
    var body: some View {
        ActivityIndicatorView(
            isVisible: $isLoading,
            type: .gradient([color, color.opacity(0.5)])
        )
        .frame(width: size, height: size)
    }
}

3. JSON で使用:

{
  "type": "LoadingSpinner",
  "isLoading": "@{isDataLoading}",
  "size": 50,
  "color": "#FF0000"
}

例 3: QR コードスキャナーの統合

1. コンバーターを生成:

sjui g converter QRScanner --attributes "@scannedCode:String,@isScanning:Bool"

2. Extensions/QRScanner.swift を修正:

import SwiftUI
import CodeScanner  // 外部 QR スキャナーライブラリ

struct QRScanner: View {
    @SwiftUI.Binding var scannedCode: String?
    @SwiftUI.Binding var isScanning: Bool
    
    var body: some View {
        if isScanning {
            CodeScannerView(
                codeTypes: [.qr],
                scanMode: .continuous,
                completion: { result in
                    switch result {
                    case .success(let code):
                        scannedCode = code.string
                        isScanning = false
                    case .failure(let error):
                        print("スキャン失敗: \(error)")
                        isScanning = false
                    }
                }
            )
        } else {
            Button("スキャン開始") {
                isScanning = true
            }
        }
    }
}

3. JSON で使用:

{
  "type": "QRScanner",
  "scannedCode": "@{qrResult}",
  "isScanning": "@{isScannerActive}"
}

ベストプラクティス

1. 属性マッピング

  • 外部コンポーネントのパラメータを SwiftJsonUI 属性にマップ
  • JSON で表現できる適切な型を使用
  • インタラクティブなコンポーネントにはバインディングプロパティの使用を検討

2. 型変換

外部ライブラリが JSON で直接サポートされない型を使用する場合:

struct DatePicker: View {
    let minDate: Date
    let maxDate: Date
    
    init(minDate: Date, maxDate: Date) {
        // JSON の文字列表現から変換
        let formatter = ISO8601DateFormatter()
        self.minDate = formatter.date(from: minDate) ?? Date()
        self.maxDate = formatter.date(from: maxDate) ?? Date()
    }
}

3. エラー処理

外部コンポーネントが失敗した場合のフォールバックを追加:

struct MapView: View {
    let latitude: Double
    let longitude: Double
    
    var body: some View {
        if ExternalMapLibrary.isAvailable {
            ExternalMapView(
                coordinate: .init(latitude: latitude, longitude: longitude)
            )
        } else {
            Text("マップは利用できません")
                .foregroundColor(.secondary)
        }
    }
}

4. 設定

コンポーネントを通じて設定を渡す:

struct RichTextEditor: View {
    let initialText: String
    @SwiftUI.Binding var htmlContent: String?
    
    var body: some View {
        ExternalRichTextView(
            text: initialText,
            configuration: .init(
                toolbar: .full,
                allowImages: true,
                allowLinks: true
            ),
            onContentChange: { html in
                htmlContent = html
            }
        )
    }
}

高度なテクニック

コールバックとイベントの処理

コールバックを持つコンポーネントの場合、バインディング更新に変換:

struct PhotoPicker: View {
    @SwiftUI.Binding var selectedImagePath: String?
    let maxSelections: Int
    
    var body: some View {
        ExternalPhotoPicker(
            maxSelections: maxSelections,
            onSelection: { images in
                // 選択をパスまたは識別子に変換
                if let firstImage = images.first {
                    selectedImagePath = saveImageAndGetPath(firstImage)
                }
            }
        )
    }
    
    private func saveImageAndGetPath(_ image: UIImage) -> String {
        // 画像を保存してパスを返す実装
        // ...
        return "path/to/image.jpg"
    }
}

UIKit コンポーネントのラッピング

外部ライブラリが UIKit コンポーネントを提供する場合、UIViewRepresentable を使用:

struct WebView: View {
    let url: String
    @SwiftUI.Binding var isLoading: Bool
    
    var body: some View {
        WebViewRepresentable(
            url: URL(string: url)!,
            isLoading: $isLoading
        )
    }
}

struct WebViewRepresentable: UIViewRepresentable {
    let url: URL
    @Binding var isLoading: Bool
    
    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        webView.navigationDelegate = context.coordinator
        return webView
    }
    
    func updateUIView(_ webView: WKWebView, context: Context) {
        webView.load(URLRequest(url: url))
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(isLoading: $isLoading)
    }
    
    class Coordinator: NSObject, WKNavigationDelegate {
        @Binding var isLoading: Bool
        
        init(isLoading: Binding<Bool>) {
            _isLoading = isLoading
        }
        
        func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            isLoading = true
        }
        
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            isLoading = false
        }
    }
}

外部コンポーネントのテスト

プレビューテスト

ラップしたコンポーネントは必ずプレビューでテスト:

#if DEBUG
struct Calendar_Previews: PreviewProvider {
    @State static var selectedDate = "2025-08-27"
    
    static var previews: some View {
        Calendar(
            selectedDate: Date(),
            onDateSelected: $selectedDate,
            showWeekNumbers: true
        )
        .padding()
    }
}
#endif

JSON テスト

統合を確認するためのテスト JSON ファイルを作成:

{
  "type": "SafeAreaView",
  "child": [
    {
      "type": "Label",
      "text": "外部コンポーネントテスト"
    },
    {
      "type": "Calendar",
      "selectedDate": "2025-08-27",
      "onDateSelected": "@{selectedDate}",
      "showWeekNumbers": true
    },
    {
      "type": "Label",
      "text": "選択日: @{selectedDate}"
    }
  ]
}

トラブルシューティング

コンポーネントがレンダリングされない

問題: 外部コンポーネントが空白または表示されない

解決策:

  • ライブラリが正しくインポートされているか確認
  • コンポーネントが正しく初期化されているか確認
  • コンポーネントが明示的なサイズを必要とする場合は .frame() モディファイアを追加
  • 外部ライブラリからのランタイムエラーをコンソールで確認

型変換の問題

問題: JSON 値がライブラリの型に正しくマップされない

解決策:

  • イニシャライザーに変換ロジックを追加
  • 複雑な変換には計算プロパティを使用
  • 文字列表現を使用してパースすることを検討

パフォーマンスの問題

問題: 外部コンポーネントがパフォーマンスの問題を引き起こす

解決策:

  • 可能な場合は遅延読み込みを使用
  • 高価な計算をキャッシュ
  • 重いコンポーネントには @StateObject の使用を検討
  • 非同期コンポーネントにローディング状態を追加

ビルドエラー

問題: 外部ライブラリ追加後にビルドが失敗

解決策:

  • ビルドフォルダをクリーンして再ビルド
  • Package.swift でバージョンの競合を確認
  • ライブラリが iOS デプロイメントターゲットをサポートしているか確認
  • ライブラリがターゲットの依存関係に追加されているか確認

関連項目

⚠️ **GitHub.com Fallback** ⚠️