External Libraries Integration - Tai-Kimura/SwiftJsonUI GitHub Wiki
This guide explains how to integrate third-party SwiftUI components from SPM libraries into your SwiftJsonUI project using custom converters.
SwiftJsonUI allows you to wrap any SwiftUI component from external libraries and make them available in your JSON layouts. This is done by:
- Adding the SPM library to your project
- Generating a custom converter with
sjui g converter - Modifying the generated Swift file to return the external component
- Using the component in JSON layouts
Add the external library to your 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")
]Generate the wrapper component with appropriate attributes:
# Example: Wrapping a Calendar component
sjui g converter Calendar --attributes "selectedDate:Date,@onDateSelected:String,showWeekNumbers:Bool"
# Example: Wrapping a Chart component
sjui g converter LineChart --attributes "dataPoints:[Double],lineColor:Color,showGrid:Bool"Edit the generated Swift file (Extensions/Calendar.swift) to use the external component:
import SwiftUI
import SwiftUICalendar // Import the external library
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 {
// Return the external library's component
CalendarView(
selectedDate: selectedDate,
showWeekNumbers: showWeekNumbers,
onDateSelected: { date in
// Convert the date to string and update binding
let formatter = DateFormatter()
formatter.dateStyle = .medium
onDateSelected = formatter.string(from: date)
}
)
.frame(height: 350) // Add any necessary modifiers
}
}Now you can use the external component in your JSON layouts:
{
"type": "Calendar",
"selectedDate": "2025-08-27",
"onDateSelected": "@{selectedDateString}",
"showWeekNumbers": true
}1. Generate the converter:
sjui g converter BarChart --attributes "data:[Double],barColor:Color,spacing:Double"2. Modify Extensions/BarChart.swift:
import SwiftUI
import Charts // External charts library
struct BarChart: View {
let data: [Double]
let barColor: Color
let spacing: Double
var body: some View {
// Use the external library's chart component
ChartView(data: data)
.barColor(barColor)
.spacing(spacing)
.frame(height: 200)
}
}3. Use in JSON:
{
"type": "BarChart",
"data": [10, 25, 15, 30, 20],
"barColor": "#007AFF",
"spacing": 4.0
}1. Generate the converter:
sjui g converter LoadingSpinner --attributes "@isLoading:Bool,size:Double,color:Color"2. Modify Extensions/LoadingSpinner.swift:
import SwiftUI
import ActivityIndicatorView // External library
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. Use in JSON:
{
"type": "LoadingSpinner",
"isLoading": "@{isDataLoading}",
"size": 50,
"color": "#FF0000"
}1. Generate the converter:
sjui g converter QRScanner --attributes "@scannedCode:String,@isScanning:Bool"2. Modify Extensions/QRScanner.swift:
import SwiftUI
import CodeScanner // External QR scanner library
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("Scanning failed: \(error)")
isScanning = false
}
}
)
} else {
Button("Start Scanning") {
isScanning = true
}
}
}
}3. Use in JSON:
{
"type": "QRScanner",
"scannedCode": "@{qrResult}",
"isScanning": "@{isScannerActive}"
}- Map the external component's parameters to SwiftJsonUI attributes
- Use appropriate types that can be represented in JSON
- Consider using binding properties for interactive components
When the external library uses types not directly supported in JSON:
struct DatePicker: View {
let minDate: Date
let maxDate: Date
init(minDate: Date, maxDate: Date) {
// Convert from string representation in JSON
let formatter = ISO8601DateFormatter()
self.minDate = formatter.date(from: minDate) ?? Date()
self.maxDate = formatter.date(from: maxDate) ?? Date()
}
}Add fallbacks for when external components fail:
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("Map not available")
.foregroundColor(.secondary)
}
}
}Pass configuration through the component:
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
}
)
}
}For components with callbacks, convert them to binding updates:
struct PhotoPicker: View {
@SwiftUI.Binding var selectedImagePath: String?
let maxSelections: Int
var body: some View {
ExternalPhotoPicker(
maxSelections: maxSelections,
onSelection: { images in
// Convert the selection to a path or identifier
if let firstImage = images.first {
selectedImagePath = saveImageAndGetPath(firstImage)
}
}
)
}
private func saveImageAndGetPath(_ image: UIImage) -> String {
// Implementation to save image and return path
// ...
return "path/to/image.jpg"
}
}If the external library provides UIKit components, use 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
}
}
}Always test your wrapped components in previews:
#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()
}
}
#endifCreate test JSON files to verify integration:
{
"type": "SafeAreaView",
"child": [
{
"type": "Label",
"text": "External Component Test"
},
{
"type": "Calendar",
"selectedDate": "2025-08-27",
"onDateSelected": "@{selectedDate}",
"showWeekNumbers": true
},
{
"type": "Label",
"text": "Selected: @{selectedDate}"
}
]
}Issue: External component appears blank or doesn't render
Solutions:
- Check if the library is properly imported
- Verify the component is initialized correctly
- Add
.frame()modifiers if the component needs explicit sizing - Check console for runtime errors from the external library
Issue: JSON values don't map correctly to library types
Solutions:
- Add conversion logic in the initializer
- Use computed properties for complex conversions
- Consider using String representations and parsing them
Issue: External component causes performance problems
Solutions:
- Use lazy loading where possible
- Cache expensive computations
- Consider using
@StateObjectfor heavy components - Add loading states for async components
Issue: Build fails after adding external library
Solutions:
- Clean build folder and rebuild
- Check Package.swift for version conflicts
- Ensure the library supports your iOS deployment target
- Verify the library is added to your target's dependencies
- Converter-Command - Detailed converter command documentation
- Custom-Components - Creating custom SwiftUI components
- Data-Binding - Data binding system
- Advanced-Features - Advanced SwiftJsonUI features