Initial Checkin
The initial release of the custom library package.
This commit is contained in:
parent
da5b32806f
commit
b7f3304c9b
|
@ -0,0 +1,23 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Do the following to make executable and run this script
|
||||
# chmod +x BuildApiDocs.sh
|
||||
# sh BuildApiDocs.sh
|
||||
|
||||
# Define macros
|
||||
PROJECT_NAME="SwiftletRadioButtonPicker"
|
||||
API_OUTPUT="Documentation/API/"
|
||||
ROOT_URL="http://appracatappra.com/api/"
|
||||
|
||||
# Make sure the output directory exists
|
||||
mkdir -p "${API_OUTPUT}"
|
||||
|
||||
# Move to project directory
|
||||
#cd "${PROJECT_DIR}"
|
||||
|
||||
# Create documentation with Jazzy
|
||||
jazzy --output "${API_OUTPUT}" --clean --swift-build-tool spm --build-tool-arguments -Xswiftc,-swift-version,-Xswiftc,5 --module ${PROJECT_NAME} --author "Appracatappra, LLC" --root-url "${ROOT_URL}" --documentation=Documentation/*.md
|
||||
|
||||
# Open project directory
|
||||
# open "${API_OUTPUT}"
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
|
@ -0,0 +1,11 @@
|
|||
# MIT License
|
||||
|
||||
Copyright © 2021 by [Appracatappra, LLC.](http://appracatappra.com)
|
||||
|
||||
--
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -5,6 +5,7 @@ import PackageDescription
|
|||
|
||||
let package = Package(
|
||||
name: "SwiftletRadioButtonPicker",
|
||||
platforms: [.iOS(.v14), .macOS(.v11), .tvOS(.v14), .watchOS(.v7)],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
|
@ -20,7 +21,8 @@ let package = Package(
|
|||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "SwiftletRadioButtonPicker",
|
||||
dependencies: []),
|
||||
dependencies: [],
|
||||
exclude:["Documentation"]),
|
||||
.testTarget(
|
||||
name: "SwiftletRadioButtonPickerTests",
|
||||
dependencies: ["SwiftletRadioButtonPicker"]),
|
||||
|
|
103
README.md
103
README.md
|
@ -1,3 +1,102 @@
|
|||
# SwiftletRadioButtonPicker
|
||||
# SwiftletRadioButtonPicker for Swift and SwiftUI
|
||||
|
||||
A description of this package.
|
||||
      
|
||||
|
||||
Creates a cross-platform Radio Button Picker that allow the user to select from a small group of options by presenting a list of `SwiftletRadioButton` objects that the user can tap on to select one items from the list.
|
||||
|
||||
<a name="Installation"></a>
|
||||
## Installation
|
||||
|
||||
**Swift Package Manager** (Xcode 11 and above)
|
||||
|
||||
1. Select **File** > **Swift Packages** > **Add Package Dependency…** from the **File** menu.
|
||||
2. Paste `https://github.com/Appracatappra/SwiftletRadioButtonPicker.git` in the dialog box.
|
||||
3. Follow the Xcode's instruction to complete the installation.
|
||||
|
||||
> Why not CocoaPods, or Carthage, or blank?
|
||||
|
||||
Supporting multiple dependency managers makes maintaining a library exponentially more complicated and time consuming.
|
||||
|
||||
Since, the **Swift Package Manager** is integrated with Xcode 11 (and greater), it's the easiest choice to support going further.
|
||||
|
||||
## Using SwiftletRadioButtonPicker
|
||||
|
||||
The `SwiftletRadioButtonPicker` works best when presenting a very limited number of options to the user. For example: selecting Male or Female. Generally this should be used for six or less options. For more options, you'll be better suited using one of the standard, built-in SwiftUI Picker views.
|
||||
|
||||
### Example
|
||||
|
||||
The following code will create a Picker with four options in iOS:
|
||||
|
||||
```swift
|
||||
SwiftletRadioButtonPicker(alignment: .grid, columns: 3, selection:"2")
|
||||
.radioButton(id: "0", name: "Option One")
|
||||
.radioButton(id: "1", name: "Option Two")
|
||||
.radioButton(id: "2", name: "Option Three")
|
||||
.radioButton(id: "3", name: "Option Four")
|
||||
```
|
||||
|
||||
Which would display a view like the following:
|
||||
|
||||

|
||||
|
||||
Optionally, you can feed any **Enum** to the `radioButtons` function and the Picker will automatically generate a list of Radio Buttons from the **Enum**.
|
||||
|
||||
### Example
|
||||
|
||||
Given the following **Enum**:
|
||||
|
||||
```swift
|
||||
/// Defines the format of the barcode to be generated.
|
||||
public enum BarcodeFormat:String, Codable, Equatable, CaseIterable, Identifiable {
|
||||
|
||||
/// Sepcifies a type 128 barcode.
|
||||
case code128 = "CICode128BarcodeGenerator"
|
||||
|
||||
/// Sepcifies a type PDF 417 barcode.
|
||||
case pdf417 = "CIPDF417BarcodeGenerator"
|
||||
|
||||
/// Sepcifies an Aztec type barcode.
|
||||
case aztec = "CIAztecCodeGenerator"
|
||||
|
||||
/// Sepcifies a QR Code type barcode.
|
||||
case qrCode = "CIQRCodeGenerator"
|
||||
|
||||
public var id:String {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
|
||||
/// Sets the enum from the given `String` value.
|
||||
/// - Parameter name: The `String` name that matches a case from the enum.
|
||||
/// - Remark: Will default to `code128` if the name cannot be found.
|
||||
public mutating func fromName(_ name:String) {
|
||||
switch(name.lowercased()) {
|
||||
case "code128":
|
||||
self = .code128
|
||||
case "pdf417":
|
||||
self = .pdf417
|
||||
case "aztec":
|
||||
self = .aztec
|
||||
case "qrcode":
|
||||
self = .qrCode
|
||||
default:
|
||||
self = .code128
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And the following code to call the Picker:
|
||||
|
||||
```swift
|
||||
SwiftletRadioButtonPicker(alignment: .grid, title:"Select barcode format:", columns: 3, selection:"code128") { button in
|
||||
card.format.fromName(button.id)
|
||||
store.refreshUI()
|
||||
}
|
||||
.radioButtons(from: BarcodeFormat.self)
|
||||
|
||||
```
|
||||
|
||||
Would create a Picker that looks similar to the following on iOS:
|
||||
|
||||

|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// SwiftletRadioButton.swift
|
||||
// Stuff To Get
|
||||
//
|
||||
// Created by Kevin Mullins on 6/16/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/// Defines a radio button that can be added to a `SwiftletRadioButtonGroup` for display in a `SwiftletRadioButtonPicker`.
|
||||
public class SwiftletRadioButton: Identifiable, Equatable, ObservableObject {
|
||||
// MARK: - Static functions
|
||||
|
||||
/// Defines a function to test and see if two `SwiftletRadioButton` are equal to eachother.
|
||||
/// - Returns: `true` if equal, else returns false.
|
||||
public static func == (lhs: SwiftletRadioButton, rhs: SwiftletRadioButton) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
/// Defines a unique id for the Radio Button.
|
||||
@Published public var id:String
|
||||
|
||||
/// Defines the display name of the Radio Button.
|
||||
@Published public var name:String
|
||||
|
||||
/// If `true`, the Radio Button is selected.
|
||||
@Published public var isSelected:Bool = false
|
||||
|
||||
/// An option tag that can be attached to the Radio Button.
|
||||
public var tag:Any? = nil
|
||||
|
||||
// MARK: - Initializers
|
||||
/// Creates a new instance of the Radio Button with the given properties.
|
||||
/// - Parameters:
|
||||
/// - id: The button's unique id.
|
||||
/// - name: The display name of the button.
|
||||
/// - isSelected: If `true`, the button will be selected.
|
||||
/// - tag: An optional tag that can be attached to the button.
|
||||
public init(id:String, name:String, isSelected:Bool = false, tag:Any? = nil) {
|
||||
// Initialize
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.isSelected = isSelected
|
||||
self.tag = tag
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// SwiftletRadioButtonAlignment.swift
|
||||
// Stuff To Get
|
||||
//
|
||||
// Created by Kevin Mullins on 6/16/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/**
|
||||
Defines how the buttons in a ``SwiftletRadionButtonPicker`` will be laid out.
|
||||
*/
|
||||
public enum SwiftletRadioButtonAlignment {
|
||||
/// Arange the buttons horizontally.
|
||||
case horizontal
|
||||
|
||||
/// Arange the buttons vertically.
|
||||
case vertical
|
||||
|
||||
/// Arange the buttons in a grid.
|
||||
case grid
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// SwiftletRadioButtonGroup.swift
|
||||
// Stuff To Get
|
||||
//
|
||||
// Created by Kevin Mullins on 6/16/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/// Contains a collection of `SwiftletRadioButton` objects that will be displayed in a `SwiftletRadioButtonPicker`. The `SwiftletRadioButtonGroup` also includes functins to add and remove button and to ensure that only one button in select at once.
|
||||
public class SwiftletRadioButtonGroup:ObservableObject {
|
||||
// MARK: - Properties
|
||||
/// The collection of `SwiftletRadioButton` objects maintained by this group.
|
||||
@Published public var buttons:[SwiftletRadioButton] = []
|
||||
|
||||
// MARK: - Initializers
|
||||
/// Creates a new instance of the object.
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
/// Adds the given `SwiftletRadioButton` to the group.
|
||||
/// - Parameter button: The `SwiftletRadioButton` to add.
|
||||
public func add(_ button:SwiftletRadioButton) {
|
||||
|
||||
if button.isSelected {
|
||||
selectNone()
|
||||
}
|
||||
|
||||
buttons.append(button)
|
||||
|
||||
ensureSelection()
|
||||
}
|
||||
|
||||
/// Removes the given `SwiftletRadioButton` from the group.
|
||||
/// - Parameter item: The `SwiftletRadioButton` to remove.
|
||||
public func remove(_ item:SwiftletRadioButton) {
|
||||
var n = 0
|
||||
|
||||
for button in buttons {
|
||||
if button == item {
|
||||
buttons.remove(at: n)
|
||||
break
|
||||
}
|
||||
n += 1
|
||||
}
|
||||
|
||||
ensureSelection()
|
||||
}
|
||||
|
||||
/// Deselects all of the `SwiftletRadioButton` objects in the group.
|
||||
public func selectNone() {
|
||||
for button in buttons {
|
||||
button.isSelected = false
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that only one `SwiftletRadioButton` is selected in the group.
|
||||
public func ensureSelection() {
|
||||
|
||||
guard buttons.count > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
for button in buttons {
|
||||
if button.isSelected {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
buttons[0].isSelected = true
|
||||
}
|
||||
|
||||
/// Selects the given `SwiftletRadioButton` and deselects all other buttons in the group
|
||||
/// - Parameter item: The `SwiftletRadioButton` to select.
|
||||
public func selectButton(_ item:SwiftletRadioButton) {
|
||||
guard buttons.count > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
selectNone()
|
||||
|
||||
for button in buttons {
|
||||
if button == item {
|
||||
button.isSelected = true
|
||||
refreshUI()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ensureSelection()
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
/// Forces any SwiftUI View this group is attached to to refresh.
|
||||
func refreshUI() {
|
||||
objectWillChange.send()
|
||||
}
|
||||
}
|
|
@ -1,6 +1,205 @@
|
|||
public struct SwiftletRadioButtonPicker {
|
||||
public private(set) var text = "Hello, World!"
|
||||
//
|
||||
// SwiftletRadioButtonPicker.swift
|
||||
// Stuff To Get
|
||||
//
|
||||
// Created by Kevin Mullins on 6/16/21.
|
||||
//
|
||||
|
||||
public init() {
|
||||
import SwiftUI
|
||||
|
||||
/// Creates a cross-platform Radio Button Picker that allow the user to select from a small group of options by presenting a list of `SwiftletRadioButton` objects that the user can tap on to select one items from the list.
|
||||
///
|
||||
/// The `SwiftletRadioButtonPicker` works best when presenting a very limited number of options to the user. For example: selecting Male or Female. Generally this should be used for six or less options. For more options, you'll be better suited using one ofthe standard, built-in SwiftUI Picker views.
|
||||
///
|
||||
/// ## Example:
|
||||
/// ```swift
|
||||
/// SwiftletRadioButtonPicker(alignment: .grid, columns: 3, selection:"2")
|
||||
/// .radioButton(id: "0", name: "Option One")
|
||||
/// .radioButton(id: "1", name: "Option Two")
|
||||
/// .radioButton(id: "2", name: "Option Three")
|
||||
/// .radioButton(id: "3", name: "Option Four")
|
||||
/// ```
|
||||
public struct SwiftletRadioButtonPicker: View {
|
||||
// MARK: - Properties
|
||||
/// Controls how Radio Buttons are laid out inside of the picker.
|
||||
public var alignment:SwiftletRadioButtonAlignment = .vertical
|
||||
|
||||
/// Defines the title show over the list of Radio Buttons.
|
||||
public var title:String = "Please select one:"
|
||||
|
||||
/// If `true`, displays a title over the collection of buttons.
|
||||
public var showTitle:Bool = true
|
||||
|
||||
/// Defines the color the title is displayed in.
|
||||
public var titleColor:Color = .black
|
||||
|
||||
/// Defines the color of the selector indicator.
|
||||
public var selectorColor:Color = .black
|
||||
|
||||
/// Defines the size of the selector indicator.
|
||||
public var selectorSize:Double = 24
|
||||
|
||||
/// Defines the name of the Symbol displayed when a Radio Button is selected.
|
||||
public var selectedSymbolName = "largecircle.fill.circle"
|
||||
|
||||
/// Defines the name of the Symbol displayed when a Radio Button is unselected.
|
||||
public var unselectedSymbolName = "circle"
|
||||
|
||||
/// Details the color the text of the Radio Button is displayed in.
|
||||
public var textColor:Color = .black
|
||||
|
||||
/// If the `alignment` is `grid`, defines the number of columns displayed in the grid.
|
||||
public var columns:Int = 2
|
||||
|
||||
/// Defines the currently selected Radio Button (by its `id`).
|
||||
public var selection:String = ""
|
||||
|
||||
/// Completion that is called when the Radio Button selection changes.
|
||||
public var selectionChanged:((SwiftletRadioButton) -> Void)? = nil
|
||||
|
||||
/// The group of `SwiftletRadioButton` objects displayed by this Picker.
|
||||
@ObservedObject public var buttonGroup:SwiftletRadioButtonGroup = SwiftletRadioButtonGroup()
|
||||
|
||||
/// The definition of the columns displayed when the `alignment` is `grid`.
|
||||
var items:[GridItem] {
|
||||
Array(repeating: .init(.adaptive(minimum: 120)), count: columns)
|
||||
}
|
||||
|
||||
// MARK: - Initializers
|
||||
public init(alignment:SwiftletRadioButtonAlignment = .vertical, title:String = "Please select one:", showTitle:Bool = true, titleColor:Color = .black, selectorColor:Color = .black, selectorSize:Double = 24, selectedSymbolName:String = "largecircle.fill.circle", unselectedSymbolName:String = "circle", textColor:Color = .black, columns:Int = 2, selection:String = "", selectionChanged:((SwiftletRadioButton) -> Void)? = nil) {
|
||||
// Initialize
|
||||
self.alignment = alignment
|
||||
self.title = title
|
||||
self.showTitle = showTitle
|
||||
self.titleColor = titleColor
|
||||
self.selectorColor = selectorColor
|
||||
self.selectorSize = selectorSize
|
||||
self.selectedSymbolName = selectedSymbolName
|
||||
self.unselectedSymbolName = unselectedSymbolName
|
||||
self.textColor = textColor
|
||||
self.columns = columns
|
||||
self.selection = selection
|
||||
self.selectionChanged = selectionChanged
|
||||
}
|
||||
|
||||
// MARK: - View Body
|
||||
/// The body of the picker.
|
||||
public var body: some View {
|
||||
VStack {
|
||||
if showTitle {
|
||||
HStack {
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
switch(alignment) {
|
||||
case .horizontal:
|
||||
HStack {
|
||||
ForEach(buttonGroup.buttons) { button in
|
||||
radioButton(button)
|
||||
.onTapGesture {
|
||||
buttonGroup.selectButton(button)
|
||||
if let selectionChanged = selectionChanged {
|
||||
selectionChanged(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.leading, .bottom, .trailing])
|
||||
case .vertical:
|
||||
VStack(alignment: .leading) {
|
||||
ForEach(buttonGroup.buttons) { button in
|
||||
radioButton(button)
|
||||
.onTapGesture {
|
||||
buttonGroup.selectButton(button)
|
||||
if let selectionChanged = selectionChanged {
|
||||
selectionChanged(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.leading, .bottom, .trailing])
|
||||
case .grid:
|
||||
VStack(alignment: .leading) {
|
||||
LazyVGrid(columns: items) {
|
||||
ForEach(buttonGroup.buttons) { button in
|
||||
radioButton(button)
|
||||
.onTapGesture {
|
||||
buttonGroup.selectButton(button)
|
||||
if let selectionChanged = selectionChanged {
|
||||
selectionChanged(button)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.leading, .bottom, .trailing])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
@ViewBuilder
|
||||
/// Generates the individual Radio Button views.
|
||||
/// - Parameter button: The Radio Button to display
|
||||
/// - Returns: The View for the `SwiftletRadioButton`.
|
||||
private func radioButton(_ button:SwiftletRadioButton) -> some View {
|
||||
HStack {
|
||||
Image(systemName: (button.isSelected) ? selectedSymbolName : unselectedSymbolName)
|
||||
.resizable()
|
||||
.frame(width: selectorSize, height: selectorSize)
|
||||
|
||||
Text(button.name)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a Radio Button to the Picker with the given properties
|
||||
/// - Parameters:
|
||||
/// - id: The unique id of the button.
|
||||
/// - name: The display name for the Radio Button.
|
||||
/// - isSelected: If `true`, this button will be selected.
|
||||
/// - tag: An optional tag to attach to the button.
|
||||
/// - Returns: This `SwiftletRadioButtonPicker` so multiple command can be chained.
|
||||
@discardableResult public func radioButton(id:String, name:String, isSelected:Bool = false, tag:Any? = nil) -> SwiftletRadioButtonPicker {
|
||||
|
||||
// Add new button to collection
|
||||
let button = SwiftletRadioButton(id: id, name: name, isSelected: isSelected, tag: tag)
|
||||
buttonGroup.add(button)
|
||||
|
||||
// Return self
|
||||
return self
|
||||
}
|
||||
|
||||
/// Adds all of the cases from the given Enum to the Picker and Radio Button options.
|
||||
/// - Parameter from: The enum type to create the list of options from (in the form `myEnum.self`).
|
||||
/// - Returns: This `SwiftletRadioButtonPicker` so multiple command can be chained.
|
||||
@discardableResult public func radioButtons<T: CaseIterable>(from enumeration: T.Type) -> SwiftletRadioButtonPicker {
|
||||
|
||||
for value in enumeration.allCases {
|
||||
let button = SwiftletRadioButton(id: "\(value)", name: "\(value)")
|
||||
button.isSelected = (button.id == selection)
|
||||
buttonGroup.add(button)
|
||||
}
|
||||
|
||||
// Return self
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Defines a sample version of the `SwiftletRadioButtonPicker` used during design.
|
||||
struct SwiftletRadioButtonPicker_Previews: PreviewProvider {
|
||||
/// Returns the sample previews for design.
|
||||
static var previews: some View {
|
||||
SwiftletRadioButtonPicker(alignment: .grid, columns: 3, selection:"2")
|
||||
.radioButton(id: "0", name: "Option One")
|
||||
.radioButton(id: "1", name: "Option Two")
|
||||
.radioButton(id: "2", name: "Option Three")
|
||||
.radioButton(id: "3", name: "Option Four")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ final class SwiftletRadioButtonPickerTests: XCTestCase {
|
|||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(SwiftletRadioButtonPicker().text, "Hello, World!")
|
||||
let button = SwiftletRadioButton(id: "0", name: "Hello, World!")
|
||||
XCTAssertEqual(button.name, "Hello, World!")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue