atacan/UsefulThingsSwiftUI
[](https://swiftpackageindex.com/atacan/UsefulThingsSwiftUI) [](https://swiftpackageindex.com/atacan/UsefulThingsSwiftUI)
Requirements
- iOS 14+ / macOS 13+
- Swift 5.9+
Installation
Add the package to your project via Swift Package Manager:
dependencies: [
.package(url: "https://github.com/atacan/UsefulThingsSwiftUI.git", from: "1.0.0")
]Or in Xcode: File > Add Package Dependencies and paste the repository URL.
Components
Async Modal Presenters
Present sheets, alerts, popovers, and dialogs using async/await instead of managing @State booleans. Each presenter type conforms to a shared ModalPresenter protocol and is driven by AsyncUIRunner.
let sheetPresenter = AnySheetPresenter()
var body: some View {
Button("Show Sheet") {
Task {
let result = try await AsyncUIRunner(presenter: sheetPresenter)
.runAny { handle in
VStack {
Text("Pick something")
Button("Done") { handle.finish("picked") }
Button("Cancel") { handle.cancel() }
}
}
print(result) // "picked"
}
}
.sheetPresenter(sheetPresenter)
}Available presenters:
| Presenter | Platform | Description | |---|---|---| | SheetPresenter | iOS, macOS | Modal sheet | | AlertPresenter | iOS 15+, macOS 12+ | Alert dialog | | DialogPresenter | iOS 15+, macOS 12+ | Confirmation dialog | | PopoverPresenter | iOS, macOS | Popover (macOS supports mouse-cursor anchoring) | | FullScreenCoverPresenter | iOS | Full screen cover | | MacWindowPresenter | macOS | Opens content in a new NSWindow |
Each has an Any* typealias (e.g. AnySheetPresenter) for convenience with AnyView.
PopupButton
A native dropdown menu — wraps NSPopUpButton on macOS and UIButton with UIMenu on iOS. Works with any type conforming to the Nameable protocol.
struct Language: Nameable {
let id = UUID()
let displayName: String
}
@State var selected = Language(displayName: "Swift")
PopupButton(
selectedKey: $selected,
keys: [
Language(displayName: "Swift"),
Language(displayName: "Rust"),
Language(displayName: "Go"),
]
)PatientTextField / PatientTextEditor
Text inputs that only update their binding when editing ends, not on every keystroke. Useful when the binding triggers expensive work.
@State var query = ""
// Cross-platform
PatientTextField("Search...", text: $query)
// macOS only — full NSTextView with configurable spell checking,
// autocomplete, link detection, etc.
PatientTextEditor(text: $query)
// macOS only — rich text via NSAttributedString
PatientAttributedTextEditor(text: $attributedText)FloatingPanel (macOS)
A floating NSPanel that stays on top, has no visible titlebar, and dismisses when it loses focus.
@State var showPanel = false
Button("Show Panel") { showPanel = true }
.floatingPanel(isPresented: $showPanel) {
Text("I'm floating")
.padding()
}OpenInWindow (macOS)
Open any SwiftUI view in a new NSWindow.
Button("Detach") {
MyDetailView()
.openInNewWindow(
title: "Detail",
rect: CGRect(x: 0, y: 0, width: 600, height: 400),
makeKey: true
)
}VisualEffectView
Translucent blur backgrounds — wraps NSVisualEffectView on macOS and UIVisualEffectView on iOS with a unified API.
VisualEffectView(
material: .sidebar,
blendingMode: .behindWindow,
state: .active,
emphasized: true
)Dynamic Layout
Components that switch between horizontal and vertical layouts based on an Axis.Set value:
@State var axis: Axis.Set = .horizontal
// Switches between HStack / VStack
DynamicStack(axis: axis, spacing: 8) {
Text("A")
Text("B")
}
// Switches between LazyHGrid / LazyVGrid
DynamicLazyGrid(axis: axis, columns: [GridItem(.flexible())]) {
ForEach(items) { item in
ItemView(item)
}
}
// Switches between horizontal / vertical ScrollView
DynamicScrollView(axis: axis) {
// ...
}View Modifiers
Corner radius with border
RoundedRectangle(cornerRadius: 0)
.cornerRadiusWithBorder(radius: 12, borderLineWidth: 2, borderColor: .blue)Glow and shadow effects
Circle().glow(color: .blue, radius: 20)
Text("Neon").multicolorGlow()
Rectangle().innerShadow(using: RoundedRectangle(cornerRadius: 8), color: .black, width: 4, blur: 4)Rotation with corrected frame
Text("Sideways").rotated(.degrees(-90))Cursor on hover (macOS)
Button("Click me") { }
.cursor(.pointingHand)Click detection (macOS)
Text("Click target")
.onClick(
onSingleClick: { /* ... */ },
onDoubleClick: { /* ... */ },
onTripleClick: { /* ... */ }
)Transitions
Pre-built asymmetric slide transitions:
MyView()
.transition(.slideFromBottom)
// also: .slideFromTop, .slideFromLeading, .slideFromTrailingSplashingButton
An animated button with a splash effect. Requires macOS 14+ / iOS 17+.
SplashingButton(imageSystemName: "heart.fill") {
// action
}Color Utilities
// Hex colors
let color = Color.hex(0xFF5733)
// Brightness
Color.blue.brighter(by: 0.2)
Color.blue.darker(by: 0.3)
// Codable wrapper with contrast calculation
let (bg, fg) = CodableCGColor.randomBGFG()
// Dynamic colors (iOS)
Color(dynamicProvider: { traits in
traits.userInterfaceStyle == .dark ? .white : .black
})Binding Utilities
// Nil-coalescing for optional bindings
TextField("Name", text: $optionalName ?? "default")
// Numeric type conversion
Slider(value: Binding<Double>.convert($intValue), in: 0...100)Other Utilities
| Component | Description | |---|---| | SizePreferenceKey | Track a view's size via preference keys | | MenuPicker | Dropdown picker for Nameable items | | TextFile | FileDocument for text-based file import/export | | Nameable | Protocol requiring id and displayName | | NSPopoverHolderView | Direct NSPopover wrapper (macOS) | | imageFrom(filePath:) | Load a SwiftUI Image from a file path |
Platform Availability
| Component | macOS | iOS | |---|---|---| | Async Modal Presenters | 12+ | 15+ | | PopupButton | 13+ | 14+ | | PatientTextField | 13+ | 14+ | | PatientTextEditor | 13+ | — | | FloatingPanel | 13+ | — | | OpenInWindow | 13+ | — | | VisualEffectView | 13+ | 14+ | | Dynamic Layout | 13+ | 14+ | | Click Detection | 13+ | — | | Cursor on Hover | 13+ | — | | SplashingButton | 14+ | 17+ | | All other utilities | 13+ | 14+ |
License
See LICENSE for details.
Package Metadata
Repository: atacan/UsefulThingsSwiftUI
Stars: 0
Forks: 0
Open issues: 0
Default branch: main
Primary language: swift
License: MIT
README: README.md