https://github.com/yusukehosonuma/swiftui-common
SwiftUI components that seem to be highly reusable.
https://github.com/yusukehosonuma/swiftui-common
swiftui swiftui-components
Last synced: about 2 months ago
JSON representation
SwiftUI components that seem to be highly reusable.
- Host: GitHub
- URL: https://github.com/yusukehosonuma/swiftui-common
- Owner: YusukeHosonuma
- License: mit
- Created: 2022-04-05T03:37:21.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2023-05-29T11:10:42.000Z (over 2 years ago)
- Last Synced: 2025-06-17T19:11:23.894Z (5 months ago)
- Topics: swiftui, swiftui-components
- Language: Swift
- Homepage:
- Size: 506 KB
- Stars: 84
- Watchers: 6
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/YusukeHosonuma/SwiftUI-Common/actions/workflows/main.yml)

[](https://twitter.com/tobi462)
# SwiftUI-Common
SwiftUI components and extensions that seem to be highly reusable.
Since this is an **experimental library**, we recommend that you copy (or use as refererence) and use the source.
## Control
HiddenLink
[HiddenLink](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/View/HiddenLink.swift) (`NavigationLink` that label is `EmptyView`)
```swift
HiddenLink(isActive: $isActive) {
ChildView()
}
```
TextEdit
[TextEdit.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/View/TextEdit.swift) (add placeholder to [TextEditor](https://developer.apple.com/documentation/swiftui/texteditor))
```swift
TextEdit("Please paste.", text: $text, font: .custom("SF Mono", size: 16))
```
ResizableImage
[ResizableImage](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/View/ResizableImage.swift)
The [Image](https://developer.apple.com/documentation/swiftui/image) that is resized only if it extends beyond the area.
```swift
Group {
ResizableImage(systemName: "swift", contentMode: .fit)
ResizableImage("island", contentMode: .fit)
ResizableImage("island", contentMode: .fill)
}
.frame(width: 140, height: 140)
.border(.red)
```

WebView
[WebView.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/View/WebView.swift) (bridge to [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview))
```swift
@StateObject var webViewState = WebViewState { _ in
// ๐ก If you want to more configuration
// webView.allowsBackForwardNavigationGestures = true
}
var body: some View {
ZStack {
WebView(url: url, state: webViewState)
if webViewState.isFirstLoading {
ProgressView()
}
// ๐ก Note: If you want to display an indicator at each page transition.
// if webViewState.isLoading {
// ProgressView()
// }
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Spacer()
// โ
Back
Button {
webViewState.goBack()
} label: {
Image(systemName: "chevron.backward")
}
.enabled(webViewState.canGoBack)
// โ
Forward
Button {
webViewState.goForward()
} label: {
Image(systemName: "chevron.forward")
}
.enabled(webViewState.canGoForward)
}
}
}
```

ActivityView
[ActivityView](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/View/UIKit/ActivityView.swift) (bridge to [UIActivityViewController](https://developer.apple.com/documentation/uikit/uiactivityviewcontroller))
```swift
@State static var isPresent = false
static var previews: some View {
Image(systemName: "square.and.arrow.up")
.sheet(isPresented: .constant(true)) {
ActivityView(activityItems: [URL(string: "https://github.com/YusukeHosonuma/SwiftUI-Common")!])
}
}
```
WindowController
[WindowController](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/View/AppKit/WindowController.swift) (bridge to [NSWindowController](https://developer.apple.com/documentation/appkit/nswindowcontroller))
T.B.D
## View extensions
[View+.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/View%2B.swift)
enabled()
```swift
@State var isEnabled = false
var body: some View {
VStack {
Button("Hello") {}
.enabled(isEnabled) // ๐ก Same as `.disabled(isEnabled == false)`
}
```
extend { ... }
```swift
Text("Hello")
.extend { content in
if #available(iOS 15, *) {
content
.environment(\.dynamicTypeSize, .xxxLarge)
} else {
content
}
}
```
when() { ... }
```swift
@State var condition = false
var body: some View {
Text("Hello")
.when(condition) {
$0.underline()
}
}
```
whenLet() { ... }
```swift
@State var textColor: Color? = .red
var body: some View {
Text("Hello")
.whenLet(textColor) { content, textColor in
content
.foregroundColor(textColor)
}
}
```
border()
```swift
Text("Hello")
.padding()
.border(.red, edge: .vertical) // default `width` = 1
.border(.blue, width: 8, edge: .leading)
```

toggleSidebar()
```swift
Button("toggle") {
toggleSidebar()
}
```
hideKeyboard()
```swift
Button("hide") {
hideKeyboard()
}
```
[View+Alert.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/View%2BAlert.swift)
alert(error: $error)
```swift
enum MyError: LocalizedError {
case warning, fatal
var errorDescription: String? {
switch self {
case .warning: return "Warning"
case .fatal: return "Fatal"
}
}
var helpMessage: String {
switch self {
case .warning: return "This is warning."
case .fatal: return "This is fatal."
}
}
}
struct ContentView: View {
@State var error: MyError? = nil
var body: some View {
VStack {
Button("Warning") { error = .warning }
Button("Fatal") { error = .fatal }
}
//
// iOS 15+
//
.alert(error: $error) {} // โ
Not need to specify `isPresented: Binding`.
.alert(error: $error) { _ in // ๐ก You can specify message.
Button("OK") {}
} message: { error in
Text(error.helpMessage)
}
//
// iOS 14+
//
.alert(error: $error)
.alert(
error: $error,
message: Text(error?.helpMessage ?? "unknown"),
dismissButton: .cancel() // Optional
)
}
}
```
[View+Debug.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/View%2BDebug.swift)
debug { ... }
```swift
func content(number: Int) -> some View {
Text("\(number)")
.debug {
print("number: \(number)") // ๐ก Any debug code.
}
}
```
print()
```swift
func content(number: Int) -> some View {
Text("\(number)")
.print("number: \(number)") // ๐ก
}
```
printOnChange()
```swift
@State var number: Int = 42
var body: some View {
Text("\(number)")
.printOnChange("number: ") { number } // ๐ก Print "number: 42" when `number is changed.
}
```
## Binding extension
[Binding+.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/Binding%2B.swift)
[SliderValue.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/SliderValue.swift) (e.g. for use `enum` in [Slider](https://developer.apple.com/documentation/swiftui/slider))
map()
```swift
@State var boolString = "false"
var body: some View {
VStack {
TextField("isOn", text: $boolString)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
//
// ๐ก Can edit `String` as `Bool`.
//
Toggle("isOn", isOn: $boolString.map( // โ
`Binding` -> `Binding`
get: { $0 == "true" },
set: { $0 ? "true" : "false" }
))
}
}
```
inverted()
```swift
@State var isEnabled = false
var body: some View {
Toggle("Disable", isOn: $isEnabled.inverted()) // โ
`true` -> `false` and `false` -> true`
Text("isEnabled: \(isEnabled ? "True" : "False")")
}
```
optional()
```swift
enum Menu: Int {
case all
case star
}
struct BindingOptionalView: View {
@SceneStorage("selection") var selection: Menu = .all
var body: some View {
let optionalSelection = $selection.optional() // ๐ก `Binding` -> `Binding
wrapped()
```swift
@Binding var optionalString: String?
var body: some View {
if let binding = $optionalString.wrapped() { // ๐ก `Binding` -> `Binding?`
TextField("placeholder", text: binding)
} else {
Text("nil")
}
}
```
case()
```swift
import CasePaths // โ
Required `pointfreeco/swift-case-paths`
import SwiftUI
enum EnumValue {
case string(String) // ๐ก Has associated-type `String`
case bool(Bool) // ๐ก Has associated-type `Bool`
}
struct CaseBindingView: View {
@State var value: EnumValue = .string("Swift")
var body: some View {
VStack {
//
// ๐ก Note: `switch` statement is only for completeness check by compiler.
// (Removal does not affect the operation)
//
switch value {
case .string:
//
// โ
Binding -> Binding?
//
if let binding = $value.case(/EnumValue.string) {
TextField("placeholder", text: binding)
}
case .bool:
//
// โ
Binding -> Binding?
//
if let binding = $value.case(/EnumValue.bool) {
Toggle("isOn", isOn: binding)
}
}
}
}
}
```
slider()
```swift
// ๐ก Want to edit by slider.
enum TextSize: Int, CaseIterable {
case xSmall = 0
case small = 1
...
var name: String {
switch self {
case .xSmall: return "xSmall"
case .small: return "small"
...
}
}
// โ
Implement `SliderValue` protocol.
extension TextSize: SliderValue {
static let sliderRange: ClosedRange = 0 ... Double(TextSize.allCases.count - 1)
var sliderIndex: Int { rawValue }
init(fromSliderIndex index: Int) {
self = Self(rawValue: index)!
}
}
struct SliderView: View {
@State var textSize: TextSize = .medium
var body: some View {
VStack {
Text("\(textSize.name)")
Slider(
value: $textSize.slider(), // ๐ก `Binding` -> `Binding`
in: TextSize.sliderRange,
step: 1
)
}
.padding()
}
}
```

## CGSize extensions
[CGSize+.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/CGSize%2B.swift)
Comparable
```swift
let a = CGSize(width: 10, height: 20)
let b = CGSize(width: 5, height: 10)
a < b // ๐ก Alias for `a.width < b.width && a.height < b.height`
```
AdditiveArithmetic
```swift
let a = CGSize(width: 10, height: 20)
let b = CGSize(width: 5, height: 10)
a + b // ๐ก Alias for `CGSize(width: a.width + b.width, height: a.height + b.height)
a - b // ๐ก Alias for `CGSize(width: a.width - b.width, height: a.height - b.height)
```
## Image extensions
[Image+.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/Image%2B.swift)
init(UIImage or NSImage)
```swift
#if os(macOS)
private typealias XImage = NSImage
#else
private typealias XImage = UIImage
#endif
struct ImageView: View {
var body: some View {
Image(image: renderImage()) // ๐ก
.resizable()
.scaledToFit()
}
private func renderImage() -> XImage {
// โ ๏ธ Assumes rendering code
#if os(macOS)
NSImage(named: "picture")!
#else
UIImage(named: "picture")!
#endif
}
}
```
## Compatible iOS 15+ (Can be used in iOS 14+)
[Section+iOS15.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/Compatible/Section%2BiOS15.swift)
[@Dismiss](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/Compatible/Dismiss.swift)
Section
```swift
Section("title") {
...
}
```
@Dismiss
```swift
// โ
Compatible to `@Environment(\.dismiss) var dismiss` in iOS 15.
@Dismiss var dismiss
// ๐ก in iOS 14+
// @Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack {
Button("Close") {
// โ
Same as `@Environment(\.dismiss)`
dismiss()
// ๐ก in iOS 14
// presentationMode.wrappedValue.dismiss()
}
}
.padding()
}
```
## Misc
[Space.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/View/Space.swift.swift)
Space(...)
```swift
// Alias for `Spacer().frame(width: 10)`
Space(width: 10)
// Alias for `Spacer().frame(height: 10)`
Space(height: 10)
```
## Concurrency extensions
[Task+.swift](https://github.com/YusukeHosonuma/SwiftUI-Common/blob/main/Sources/SwiftUICommon/Extension/Task%2B.swift)
sleep()
```swift
Task {
try await Task.sleep(seconds: 1) // 1 s
try await Task.sleep(milliseconds: 500) // 500 ms
}
```
## Install
If you want.
```swift
let package = Package(
dependencies: [
.package(url: "https://github.com/YusukeHosonuma/SwiftUI-Common.git", from: "1.0.0"),
],
targets: [
.target(name: "", dependencies: [
.product(name: "SwiftUICommon", package: "SwiftUI-Common"),
]),
]
)
```
## Development
Setup:
```sh
make setup
```
Format:
```sh
make format
```
## Links
- This library is used in the following:
- [SwiftUI-Simulator](https://github.com/YusukeHosonuma/SwiftUI-Simulator)
- [UserDefaults-Browser](https://github.com/YusukeHosonuma/UserDefaultsBrowser)
- [Swift-Evolution-Browser](https://github.com/YusukeHosonuma/Swift-Evolution-Browser)
- [E2DC](https://github.com/YusukeHosonuma/E2DC)
- Document (Japanese):
- [Effective SwiftUI ๅ่ฃ๏ผไปฎ่ชฌ๏ผ](https://github.com/YusukeHosonuma/Effective-SwiftUI)
## Author
Yusuke Hosonuma / [@tobi462](https://twitter.com/tobi462)