Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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 9 hours 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 (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2023-05-29T11:10:42.000Z (over 1 year ago)
- Last Synced: 2023-09-11T05:50:19.458Z (over 1 year ago)
- Topics: swiftui, swiftui-components
- Language: Swift
- Homepage:
- Size: 506 KB
- Stars: 72
- Watchers: 5
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[![Build](https://github.com/YusukeHosonuma/SwiftUI-Common/actions/workflows/main.yml/badge.svg)](https://github.com/YusukeHosonuma/SwiftUI-Common/actions/workflows/main.yml)
![iOS 14+](https://img.shields.io/badge/iOS-14+-4BC51D.svg?style=flat)
[![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Ftobi462)](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 = falsestatic 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 = falsevar 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 = falsevar body: some View {
Text("Hello")
.when(condition) {
$0.underline()
}
}
```whenLet() { ... }
```swift
@State var textColor: Color? = .redvar 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, fatalvar 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? = nilvar 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 = 42var 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 = falsevar 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 = .allvar 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 SwiftUIenum 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 = .mediumvar 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
#endifstruct 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 presentationModevar 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
}
```
## InstallIf 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)