External Libraries Integration ja - Tai-Kimura/SwiftJsonUI GitHub Wiki
このガイドでは、SPM ライブラリからのサードパーティ SwiftUI コンポーネントを、カスタムコンバーターを使用して SwiftJsonUI プロジェクトに統合する方法を説明します。
SwiftJsonUI では、外部ライブラリの SwiftUI コンポーネントをラップして、JSON レイアウトで使用できるようにすることができます。これは以下の手順で実現されます:
- SPM ライブラリをプロジェクトに追加
-
sjui g converterでカスタムコンバーターを生成 - 生成された Swift ファイルを修正して外部コンポーネントを返す
- JSON レイアウトでコンポーネントを使用
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")
]適切な属性でラッパーコンポーネントを生成:
# 例:Calendar コンポーネントのラッピング
sjui g converter Calendar --attributes "selectedDate:Date,@onDateSelected:String,showWeekNumbers:Bool"
# 例:Chart コンポーネントのラッピング
sjui g converter LineChart --attributes "dataPoints:[Double],lineColor:Color,showGrid:Bool"生成された 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) // 必要なモディファイアを追加
}
}これで JSON レイアウトで外部コンポーネントを使用できます:
{
"type": "Calendar",
"selectedDate": "2025-08-27",
"onDateSelected": "@{selectedDateString}",
"showWeekNumbers": true
}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
}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"
}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}"
}- 外部コンポーネントのパラメータを SwiftJsonUI 属性にマップ
- JSON で表現できる適切な型を使用
- インタラクティブなコンポーネントにはバインディングプロパティの使用を検討
外部ライブラリが 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()
}
}外部コンポーネントが失敗した場合のフォールバックを追加:
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)
}
}
}コンポーネントを通じて設定を渡す:
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 コンポーネントを提供する場合、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 ファイルを作成:
{
"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 デプロイメントターゲットをサポートしているか確認
- ライブラリがターゲットの依存関係に追加されているか確認
- Converter-Command-ja - 詳細なコンバーターコマンドドキュメント
- Custom-Components-ja - カスタム SwiftUI コンポーネントの作成
- Data-Binding - データバインディングシステム
- Advanced-Features - 高度な SwiftJsonUI 機能