/BDUIKnit

A Swift Package Manager packed with SwiftUI custom reusable UI components and extensions.

Primary LanguageSwiftMIT LicenseMIT

BDUIKnit

Swift Package Manager Twitter

BDUIKnit

BDUIKnit is a collection of SwiftUI custom reusable UI components and extensions packed in a Swift Package. BDUIKnit is completely written in Swift with no dependencies. The package is fully documented with some exceptions of internal objects.

Goals

  • To collect my personal custom reusable UI components and extensions and put them in one place.
  • To create custom reusable UI components and share them.
  • To explore, learn new techniques, and share what I learnt building these components & extensions.

Get Started

Installation

To add BDUIKnit to your project:

  • Open your project in Xcode
  • Go to File > Swift Packages > Add Package Dependency...
  • Search for BDUIKnit and follow Xcode's installation dialogs.

Quick Introduction

BDUIKnit follows MVVM design pattern; therefore, most Views will have their corresponding View Models. View models are either class or struct, so use the appropriate @ObservedObject, @State, or @Binding as needed.

New to MVVM? Fear not. Try to read the below codes, if you can make sense of what they are doing, you are ready to use BDUIKnit.

// create a view model that controls the tray view
let trayViewModel = BDButtonTrayViewModel()
trayViewModel.mainItem = createTrayMainItem()
trayViewModel.items = createTrayItems()

trayViewModel.expanded = true
trayViewModel.shouldDisableMainItemWhenExpanded = true

trayViewModel.trayColor = Color(.systemBackground)
trayViewModel.itemActiveColor = Color.accentColor

// pass the view model to the tray view to render
BDButtonTrayView(viewModel: trayViewModel)

// while the tray view is displaying, update the view model
trayViewModel.expanded = false

// the tray view is now collapsed

Documentations

BDButtonTrayView

A tray-like view that is normally pinned to the bottom-trailing of a scene.

Tray item now supports more animations.

Regular Vertical Size Class

BDButtonTrayPreview-1

Compact Vertical Size Class

BDButtonTrayPreview-2

Tray Item Animations

BDButtonTrayItemAnimation

Quick Start:

// Add tray view to a view when content ignores safe area

struct SomeView: View {

    @State private var trayViewModel = BDButtonTrayViewModel()

    var body: some View {
        ZStack {
            SomeContent()
                .edgesIgnoringSafeArea(.all)

            BDButtonTrayView(viewModel: trayViewModel)
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
                .padding(16)
                .onAppear(perform: setupTrayViewModel)
        }
    }

    func setupTrayViewModel() {
        // setup tray view model...
    }
}

For sample code, see ButtonTrayViewPreview

BDModalTextField

A text field view intended to be used as a modal presentation sheet when need to get inputs from user.

BDModalTextFieldPreview

Quick Start:

For sample code, see ModalTextFieldPreview

BDModalTextView

A text view intended to be used as a modal presentation sheet when need to get inputs from user.

BDModalTextViewPreview

Quick Start:

For sample code, see ModalTextViewPreview

BDPersist Property Wrapper

A property wrapper that stores value in a given store. For example, UserDefaults.

Quick Start:

// Store username in UserDefaults

@BDPersist(in: .userDefaults, key: "username", default: "")
var username: String
// Add post notification when username changed

static let nUsernameDidChange = Notification.Name("nUsernameDidChange")

@BDPersist(in: .userDefaults, key: "username", default: "", post: nUsernameDidChange)
var username: String
// Use optional value and NSUbiquitousKeyValueStore
// see docs for how to enable ubiquitous store

@BDPersist(in: .ubiquitousStore, key: "highScore", default: nil)
var highScore: Int?
// Use type-safe key

// create an enum
// conform to BDPersistKey
// implement the required prefix property
enum Keys: BDPersistKey {
    var prefix: String { "some.prefix." }
    case autoplay
    case autosave
}

// the key is 'some.prefix.autoplay'
@BDPersist(in: .userDefaults, key: Keys.autoplay, default: true)
var autoplay: Bool

// the key is 'some.prefix.autosave'
@BDPersist(in: .userDefaults, key: Keys.autosave, default: false)
var autosave: Bool
// Use custom store

// conform to BDPersistStorable
// implement required methods
class CustomStore: BDPersistStorable {
    // implementations...
}

@BDPersist(in: .custom(CustomStore()), key: "username", default: "")
var username: String

For sample code, see PersistPropertyWrapperPreview

BDPresentationItem

An object used to present sheet. It provide an easy way to store previous dismissed sheet if needed.

Quick Start

// Example code with Enum conforms to BDPresentationSheetItem

struct UserProfileView: View {

    // conform to BDPresentationSheetItem or Identifiable
    enum Sheet: BDPresentationSheetItem {
        case modalTextField
        case modalTextView
    }

    @State private var sheet = BDPresentationItem<Sheet>()

    var body: some View {
        Form {
            Button("Edit Username") {
                self.sheet.present(.modalTextField)
            }

            Button("Edut UserBio") {
                self.sheet.current = .modalTextView
            }
        }
        .onAppear(perform: setupOnAppear)
        .sheet(item: $sheet.current, onDismiss: sheetDismissed, content: presentationSheet)
    }

    func setupOnAppear() {
        // if need to access sheet.previous on dismissed
        sheet.shouldStorePrevious = true
    }

    func presentationSheet(for sheet: Sheet) -> some View {
        switch sheet {
        case .modalTextField: return AnyView(...)
        case .modalTextView: return AnyView(...)
        }
    }

    func sheetDismissed() {
        if sheet.previous == .modalTextField {
            // do something
        }
    }
}

For sample code, see PresentationItemPreview

Extension

// Create color from hex

Color(hex: "BDA12A") // a Color

UIColor(hex: "#bda12a") // a UIColor

UIColor(hex: "purple") // fatal error: create color with invalid hex: 'purple'