🐚 The "horn of plenty" – a symbol of abundance.
A SwiftUI utility library that extends the Cornucopia ecosystem with reusable components, view modifiers, and tools for building polished SwiftUI applications across all Apple platforms.
- iOS 16+
- macOS 13+
- tvOS 16+
- watchOS 9+
Add CornucopiaSUI as a dependency to your Package.swift
:
dependencies: [
.package(url: "https://github.com/Cornucopia-Swift/CornucopiaSUI", branch: "master")
]
Or add it through Xcode:
- File → Add Package Dependencies...
- Enter the repository URL:
https://github.com/Cornucopia-Swift/CornucopiaSUI
- Select the master branch
- Add to your target
CC_task
- Persistent background tasks that survive view hierarchy changesCC_onFirstAppear
- Execute code only on first view appearanceCC_measureSize
- Measure view dimensionsCC_blink
- Blinking animation effectsCC_conditionalView
- Conditional view renderingCC_debouncedTask
- Debounced task executionCC_invisibleNavigation
- Hide navigation elementsCC_debugPrint
- Debug printing for developmentCC_presentationDetentAutoHeight
- Auto-sizing sheet presentation
BusyButton
- Buttons with built-in loading statesMarqueeScrollView
- Generic auto-scrolling container for any SwiftUI content (iOS 17+)MarqueeText
- Smoothly scrolling text for long contentBlendingTextLabel
- Text with blending animationsSynchronizedBlendingTextLabel
- Blended text synchronized across a groupNetworkAwareTextField
- Text fields that adapt to network stateVINTextField
- Vehicle Identification Number input with validation and formattingSingleAxisGeometryReader
- Geometry reading for single axisImagePickerView
- UIKit image picker integration
NavigationController
- Type-safe navigation stack management with programmatic control
ObservableBusyness
- Debounced busy state managementObservableReachability
- Network connectivity monitoringObservableLocalNetworkAuthorization
- Local network permission state
AudioPlayer
- Simple audio playback utilities
KeyboardAwareness
- Keyboard state monitoringUIImage+Resize
- Image resizing extensionsUIApplication+AsyncIdleTimer
- Async idle timer utilities
- Color+ForeignFrameworkColors - Color compatibility helpers
- Image+ForeignFrameworks - Image framework integrations
- ProcessInfo+Previews - Preview environment helpers
import SwiftUI
import CornucopiaSUI
struct ContentView: View {
@State private var isLoading = false
var body: some View {
BusyButton(isBusy: $isLoading, title: "Upload Data") {
// Your async operation here
try await uploadData()
}
.buttonStyle(.borderedProminent)
}
}
import SwiftUI
import CornucopiaSUI
struct AppView: View {
@StateObject private var navigationController = NavigationController()
var body: some View {
NavigationStack(path: $navigationController.path) {
ContentView()
.environment(\.CC_navigationController, navigationController)
}
}
}
struct ContentView: View {
@Environment(\.CC_navigationController) private var navigation
var body: some View {
Button("Go to Detail") {
navigation?.push(DetailDestination.profile(userId: 123))
}
}
}
enum DetailDestination: Hashable {
case profile(userId: Int)
case settings
}
import SwiftUI
import CornucopiaSUI
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
// Any SwiftUI content can scroll
MarqueeScrollView {
Label("Long configuration settings text", systemImage: "gear")
}
.frame(width: 200)
// Complex layouts work too
MarqueeScrollView(startDelay: 1.0) {
HStack {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
Text("★★★★★ Excellent product with amazing reviews!")
.bold()
}
}
.frame(width: 250)
}
}
}
import SwiftUI
import CornucopiaSUI
struct TickerView: View {
var body: some View {
MarqueeText(
"This is a very long text that will scroll horizontally when it doesn't fit",
startDelay: 2.0
)
.font(.headline)
.frame(width: 200)
}
}
Synchronize the text-blending animation of multiple labels, independent of when each view appears. Wrap a subtree with CC_blendingSyncGroup
, then use SynchronizedBlendingTextLabel
inside it.
import SwiftUI
import CornucopiaSUI
struct StatusRow: View {
var body: some View {
VStack(alignment: .leading, spacing: 12) {
// These two labels will advance in lockstep
SynchronizedBlendingTextLabel(["Loading", "Processing", "Working"], duration: 1.5)
SynchronizedBlendingTextLabel(["Please wait", "Almost there", "Finalizing"], duration: 1.5)
}
.CC_blendingSyncGroup("status", duration: 1.5) // group controls the cadence
}
}
Notes:
- The group’s
duration
controls the cycle timing for all members of that group. - A
SynchronizedBlendingTextLabel
outside any group automatically falls back toBlendingTextLabel
and uses its ownduration
. - Multiple independent groups can coexist:
HStack(spacing: 24) {
VStack {
Text("Fast")
SynchronizedBlendingTextLabel(["●", "●", "●"], duration: 0.8)
}
.CC_blendingSyncGroup("fast", duration: 0.8)
VStack {
Text("Slow")
SynchronizedBlendingTextLabel(["◆", "◆", "◆"], duration: 2.5)
}
.CC_blendingSyncGroup("slow", duration: 2.5)
}
Tip: See the preview “SynchronizedBlendingTextLabel - Scroll Sync Showcase” for a scroll-heavy example that clearly demonstrates synchronization when rows appear at different times.
import SwiftUI
import CornucopiaSUI
struct DataSyncView: View {
@State private var syncProgress = 0.0
var body: some View {
VStack {
ProgressView(value: syncProgress)
Text("Syncing data…")
}
.CC_task {
// This task continues even when view is removed from hierarchy
await performLongRunningSyncOperation()
}
}
}
import SwiftUI
import CornucopiaSUI
class DataManager: ObservableObject {
private let busyness = ObservableBusyness(debounceInterval: .milliseconds(300))
var isBusy: Bool { busyness.isBusy }
func loadData() async {
busyness.enterBusy()
defer { busyness.leaveBusy() }
// Perform data loading
await performNetworkRequest()
}
}
import SwiftUI
import CornucopiaSUI
struct AnalyticsView: View {
var body: some View {
ContentView()
.CC_onFirstAppear {
// Track screen view only once, not on every appear
Analytics.trackScreenView("content_screen")
}
}
}
import SwiftUI
import CornucopiaSUI
struct NetworkStatusView: View {
@StateObject private var reachability = ObservableReachability()
var body: some View {
VStack {
Text("Network Status: \(reachability.isConnected ? "Connected" : "Offline")")
.foregroundColor(reachability.isConnected ? .green : .red)
}
.onAppear {
reachability.startMonitoring()
}
}
}
import SwiftUI
import CornucopiaSUI
struct VehicleEntryView: View {
@State private var vin = ""
@State private var validationState: VINTextField.ValidationState = .empty
@FocusState private var isFocused: Bool
var body: some View {
VStack(spacing: 20) {
VINTextField($vin, focused: $isFocused, validationState: $validationState)
// Respond to validation state changes
switch validationState {
case .valid(let validVin, let components):
VStack {
Text("Valid VIN: \(validVin)")
.foregroundColor(.green)
if let modelYear = components.modelYear {
Text("Model Year: \(modelYear)")
.font(.caption)
}
}
case .invalidCheckDigit:
Text("VIN contains errors - please verify")
.foregroundColor(.red)
default:
EmptyView()
}
}
.padding()
}
}
CornucopiaSUI builds upon CornucopiaCore and follows these key patterns:
- Observable State Management - Uses
ObservableObject
for reactive state - View Modifier Pattern - Custom modifiers with
CC_
prefix for easy discovery - Type-Safe Navigation - Centralized navigation with compile-time safety
- Platform Consistency - Unified APIs across all Apple platforms
# Run all tests
swift test
# Run specific test
swift test --filter CornucopiaSUITests/testExample
# Build for all platforms
swift build
# Build for specific platform (iOS Simulator)
swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios16.0-simulator"
- CornucopiaCore - Foundation types like
Logger
,Protected
, andBusynessObserver
Contributions are welcome! Feel free to submit issues, feature requests, or pull requests. Please follow the existing code style and include tests for new functionality.
Available under the MIT License. Feel free to use in your projects!