Swiftpack.co - YusukeHosonuma/SwiftUI-Common as Swift Package

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
See all packages published by YusukeHosonuma.
YusukeHosonuma/SwiftUI-Common 1.0.0
SwiftUI components that seem to be highly reusable.
โญ๏ธ 28
๐Ÿ•“ 4 days ago
iOS macOS
.package(url: "https://github.com/YusukeHosonuma/SwiftUI-Common.git", from: "1.0.0")

Build iOS 14+ Twitter

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

TextEdit

TextEdit.swift (add placeholder to TextEditor)

TextEdit("Please paste.", text: $text, font: .custom("SF Mono", size: 16))
ResizableImage

ResizableImage

The Image that is resized only if it extends beyond the area.


Group {
    ResizableImage(systemName: "swift", contentMode: .fit)
    ResizableImage("island", contentMode: .fit)
    ResizableImage("island", contentMode: .fill)
}
.frame(width: 140, height: 140)
.border(.red)
WebView

WebView.swift (bridge to WKWebView)

@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)
        }
    }
}
image
ActivityView

ActivityView (bridge to UIActivityViewController)

@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 (bridge to NSWindowController)
T.B.D

View extensions

View+.swift

enabled()
@State var isEnabled = false

var body: some View {
    VStack {
        Button("Hello") {}
            .enabled(isEnabled) // ๐Ÿ’ก Same as `.disabled(isEnabled == false)`
}
extend { ... }
Text("Hello")
    .extend { content in
        if #available(iOS 15, *) {
            content
                .environment(\.dynamicTypeSize, .xxxLarge)
        } else {
            content
        }
    }
when() { ... }
@State var condition = false

var body: some View {
    Text("Hello")
        .when(condition) {
            $0.underline()
        }
}
whenLet() { ... }
@State var textColor: Color? = .red

var body: some View {
    Text("Hello")
        .whenLet(textColor) { content, textColor in
            content
                .foregroundColor(textColor)
        }
}
border()
Text("Hello")
    .padding()
    .border(.red, edge: .vertical) // default `width` = 1
    .border(.blue, width: 8, edge: .leading)
image
toggleSidebar()
Button("toggle") {
    toggleSidebar()
}
hideKeyboard()
Button("hide") {
    hideKeyboard()
}

View+Debug.swift

debug { ... }
func content(number: Int) -> some View {
    Text("\(number)")
        .debug {
            print("number: \(number)") // ๐Ÿ’ก Any debug code.
        }
}
print()
func content(number: Int) -> some View {
    Text("\(number)")
        .print("number: \(number)") // ๐Ÿ’ก
}
printOnChange()
@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
SliderValue.swift (e.g. for use enum in Slider)

map()
@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<String>` -> `Binding<Bool>`
            get: { $0 == "true" },
            set: { $0 ? "true" : "false" }
        ))
    }
}
optional()
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<Menu>` -> `Binding<Menu?`
        NavigationView {
            List {
                NavigationLink(tag: Menu.all, selection: optionalSelection, destination: { Text("1") }) {
                    Text("One")
                }
                NavigationLink(tag: Menu.star, selection: optionalSelection, destination: { Text("2") }) {
                    Text("Two")
                }
            }
        }
    }
}
wrapped()
@Binding var optionalString: String?

var body: some View {
    if let binding = $optionalString.wrapped() { // ๐Ÿ’ก `Binding<String?>` -> `Binding<String>?`
        TextField("placeholder", text: binding)
    } else {
        Text("nil")
    }
}
case()
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<Value> -> Binding<String>?
                //
                if let binding = $value.case(/EnumValue.string) {
                    TextField("placeholder", text: binding)
                }

            case .bool:
                //
                // โœ… Binding<Value> -> Binding<Int>?
                //
                if let binding = $value.case(/EnumValue.bool) {
                    Toggle("isOn", isOn: binding)
                }
            }
        }
    }
}
slider()
// ๐Ÿ’ก 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<Double> = 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<TextSize>` -> `Binding<Double>`
                in: TextSize.sliderRange,
                step: 1
            )
        }
        .padding()
    }
}
image

CGSize extensions

CGSize+.swift

Comparable
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
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

init(UIImage or NSImage)
#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
@Dismiss

Section
Section("title") {
    ...
}
@Dismiss
// โœ… 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()
}

Concurrency extensions

Task+.swift

sleep()
Task {
    try await Task.sleep(seconds: 1)        // 1 s
    try await Task.sleep(milliseconds: 500) // 500 ms
}

Install

If you want.

let package = Package(
    dependencies: [
        .package(url: "https://github.com/YusukeHosonuma/SwiftUI-Common.git", from: "1.0.0"),
    ],
    targets: [
        .target(name: "<your-target-name>", dependencies: [
             .product(name: "SwiftUICommon", package: "SwiftUI-Common"),
        ]),
    ]
)

Links

Author

Yusuke Hosonuma / @tobi462

GitHub

link
Stars: 28
Last commit: Yesterday
jonrohan Something's broken? Yell at me @ptrpavlik. Praise and feedback (and money) is also welcome.

Release Notes

1.0.0
4 days ago

Now conforms to the semantic version.

Control

image

Extension

  • CGSize+.swift
    • contains(_ other: CGSize) -> Bool
  • View+
    • frame(size: CGSize?) -> some View
    • sizePreference() -> some View
    • onChangeSizePreference(_ perform: @escaping (CGSize) -> Void) -> some View
    • boundsPreference() -> some View (iOS 15+)
    • onChangeBoundsPreference(_ geometry: GeometryProxy, perform: @escaping (CGSize) -> Void) -> some View (iOS 15+)

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics