Skip to content

Commit

Permalink
feature/improve-init-protocol-constraints (#4)
Browse files Browse the repository at this point in the history
- Add initializers that take a closure to map from option values to selection values
- Add initializers that allow for option values and selection values to be the same type
- Relax protocol requirements on initializers to allow more generalized use
  • Loading branch information
roanutil authored Oct 2, 2022
1 parent 3b79cd4 commit 3bb0db5
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 25 deletions.
169 changes: 145 additions & 24 deletions Sources/PickBetter/BetterPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,79 +59,200 @@ public struct BetterPicker<SelectionBox, ItemContent>: View where SelectionBox:
return styledBody
}

private init(items: [ItemTuple], selection: Binding<SelectionBox>) {
fileprivate init(items: [ItemTuple], selection: Binding<SelectionBox>) {
self.items = items
_selection = selection
}

/// Generates the `ItemTuple`'s from a collection of `Identifiable` items
fileprivate static func itemsFromData<Data>(
_ data: Data,
selectionValue: @escaping (Data.Element) -> SelectionValue,
content: @MainActor @escaping (Data.Element) -> ItemContent
) -> [ItemTuple] where Data: Sequence {
data.map { item in (selectionValue(item), { content(item) }) }
}

// MARK: Init with closure to map from Option to Selection

/// Initializer for an array of identifiable elements where there is always one selection (never nil)
/// - Parameters
/// - data: `Data` -- `RandomAccessCollection` where `Data.Element: Identifiable`, `Data.Element.ID == SelectionValue`
/// - content: `(Data.Element) -> ItemContent` View builder for a cell
/// - Returns
/// - `[ItemTuple]`
private static func itemsFromData<Data>(
/// - data: Data -- `Sequence`
/// - selectionValue: `@escaping (Data.Element) -> SelectionValue`
/// - selection: `Binding<SelectionValue>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data, UnwrappedSelection>(
_ data: Data,
selectionValue: @escaping (Data.Element) -> SelectionValue,
selection: Binding<SelectionValue>,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, SelectionBox == SingleSelectionWrapper<UnwrappedSelection> {
let selectionBinding: Binding<SingleSelectionWrapper<SelectionValue>> = Binding(
get: { SingleSelectionWrapper(value: selection.wrappedValue) },
set: { selection.wrappedValue = $0.value }
)
self.init(
items: Self.itemsFromData(data, selectionValue: selectionValue, content: content),
selection: selectionBinding
)
}

/// Initializer for an array of identifiable elements where the selection is a single optional value
/// - Parameters
/// - data: Data -- `Sequence`
/// - selectionValue: `@escaping (Data.Element) -> SelectionValue`
/// - selection: `Binding<SelectionValue?>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data, WrappedSelectionValue>(
_ data: Data,
selectionValue: @escaping (Data.Element) -> SelectionValue,
selection: Binding<WrappedSelectionValue?>,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, SelectionBox == WrappedSelectionValue? {
self.init(
items: Self.itemsFromData(data, selectionValue: selectionValue, content: content),
selection: selection
)
}
}

extension BetterPicker where SelectionBox: Sequence {
/// Initializer for an array of identifiable elements where multiple selections can be made
/// - Parameters
/// - data: Data -- `Sequence`
/// - selectionValue: `@escaping (Data.Element) -> SelectionValue`
/// - selection: `Binding<SelectionBox>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data>(
_ data: Data,
selectionValue: @escaping (Data.Element) -> SelectionValue,
selection: Binding<SelectionBox>,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, Data.Element == SelectionBox.SelectionValue {
self.init(
items: Self.itemsFromData(data, selectionValue: selectionValue, content: content),
selection: selection
)
}
}

// MARK: Init where Selection == Option

extension BetterPicker {
/// Initializer for an array of identifiable elements where there is always one selection (never nil)
/// - Parameters
/// - data: Data -- `Sequence` where `Data.Element == SelectionValue`
/// - selection: `Binding<SelectionValue>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data, UnwrappedSelection>(
_ data: Data,
selection: Binding<SelectionValue>,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, Data.Element == SelectionValue,
SelectionBox == SingleSelectionWrapper<UnwrappedSelection>
{
let selectionBinding: Binding<SingleSelectionWrapper<SelectionValue>> = Binding(
get: { SingleSelectionWrapper(value: selection.wrappedValue) },
set: { selection.wrappedValue = $0.value }
)
self.init(
items: Self.itemsFromData(data, selectionValue: { $0 }, content: content),
selection: selectionBinding
)
}

/// Initializer for an array of identifiable elements where the selection is a single optional value
/// - Parameters
/// - data: Data -- `Sequence` where `Data.Element == SelectionValue`
/// - selection: `Binding<SelectionValue?>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data, WrappedSelectionValue>(
_ data: Data,
content: @escaping (Data.Element) -> ItemContent
) -> [ItemTuple] where Data: RandomAccessCollection,
Data.Element: Identifiable, Data.Element.ID == SelectionValue
selection: Binding<WrappedSelectionValue?>,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, Data.Element == WrappedSelectionValue,
SelectionBox == WrappedSelectionValue?
{
data.map { item in (item.id, { content(item) }) }
self.init(
items: Self.itemsFromData(data, selectionValue: { $0 }, content: content),
selection: selection
)
}
}

extension BetterPicker where SelectionBox: Sequence {
/// Initializer for an array of identifiable elements where multiple selections can be made
/// - Parameters
/// - data: Data -- `Sequence` where `Data.Element == SelectionValue`
/// - selection: `Binding<SelectionBox>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data>(
_ data: Data,
selection: Binding<SelectionBox>,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, Data.Element == SelectionBox.SelectionValue {
self.init(
items: Self.itemsFromData(data, selectionValue: { $0 }, content: content),
selection: selection
)
}
}

// MARK: Init where Option: Identifiable and Selection == Option.ID

extension BetterPicker {
/// Initializer for an array of identifiable elements where there is always one selection (never nil)
/// - Parameters
/// - data: Data -- `RandomAccessCollection` where `Data.Element: Identifiable`, `Data.Element.ID == SelectionValue`
/// - data: Data -- `Sequence` where `Data.Element: Identifiable`, `Data.Element.ID == SelectionValue`
/// - selection: `Binding<SelectionValue>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data, UnwrappedSelection>(
_ data: Data,
selection: Binding<SelectionValue>,
@ViewBuilder content: @escaping (Data.Element) -> ItemContent
) where Data: RandomAccessCollection, Data.Element: Identifiable, Data.Element.ID == SelectionValue,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, Data.Element: Identifiable, Data.Element.ID == SelectionValue,
SelectionBox == SingleSelectionWrapper<UnwrappedSelection>
{
let selectionBinding: Binding<SingleSelectionWrapper<SelectionValue>> = Binding(
get: { SingleSelectionWrapper(value: selection.wrappedValue) },
set: { selection.wrappedValue = $0.value }
)
self.init(items: Self.itemsFromData(data, content: content), selection: selectionBinding)
self.init(items: Self.itemsFromData(data, selectionValue: \.id, content: content), selection: selectionBinding)
}

/// Initializer for an array of identifiable elements where the selection is a single optional value
/// - Parameters
/// - data: Data -- `RandomAccessCollection` where `Data.Element: Identifiable`, `Data.Element.ID == SelectionValue`
/// - data: Data -- `Sequence` where `Data.Element: Identifiable`, `Data.Element.ID == SelectionValue`
/// - selection: `Binding<SelectionValue?>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data, WrappedSelectionValue>(
_ data: Data,
selection: Binding<WrappedSelectionValue?>,
@ViewBuilder content: @escaping (Data.Element) -> ItemContent
) where Data: RandomAccessCollection, Data.Element: Identifiable, Data.Element.ID == WrappedSelectionValue,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, Data.Element: Identifiable, Data.Element.ID == WrappedSelectionValue,
SelectionBox == WrappedSelectionValue?
{
self.init(
items: Self.itemsFromData(data, content: content),
items: Self.itemsFromData(data, selectionValue: \.id, content: content),
selection: selection
)
}
}

extension BetterPicker where SelectionBox: Collection {
extension BetterPicker where SelectionBox: Sequence {
/// Initializer for an array of identifiable elements where multiple selections can be made
/// - Parameters
/// - data: Data -- `RandomAccessCollection` where `Data.Element: Identifiable`, `Data.Element.ID == SelectionValue`
/// - data: Data -- `Sequence` where `Data.Element: Identifiable`, `Data.Element.ID == SelectionValue`
/// - selection: `Binding<SelectionBox>`
/// - content: `(Data.Element) -> ItemContent` -- View builder for a cell
public init<Data>(
_ data: Data,
selection: Binding<SelectionBox>,
@ViewBuilder content: @escaping (Data.Element) -> ItemContent
) where Data: RandomAccessCollection, Data.Element: Identifiable, SelectionBox.Element == Data.Element.ID,
@ViewBuilder content: @MainActor @escaping (Data.Element) -> ItemContent
) where Data: Sequence, Data.Element: Identifiable, SelectionBox.Element == Data.Element.ID,
SelectionBox.Element == SelectionBox.SelectionValue
{
self.init(
items: Self.itemsFromData(data, content: content),
items: Self.itemsFromData(data, selectionValue: \.id, content: content),
selection: selection
)
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/PickBetter/StyleBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import Foundation
import SwiftUI

/// `resultBuilder` implementation for `BetterPickerStyle` that enables convenient choosing of a style in logic without causing problems with different types.
/// `resultBuilder` implementation for `BetterPickerStyle` that enables convenient choosing of a style in logic without
// causing problems with different types.
@resultBuilder
public enum StyleBuilder {
public static func buildBlock<Style: BetterPickerStyle>(_ style: Style) -> Style {
Expand Down

0 comments on commit 3bb0db5

Please sign in to comment.