Initial Checkin

The initial release of the custom library package.
This commit is contained in:
Kevin Mullins 2021-06-17 14:18:21 -05:00
parent da5b32806f
commit b7f3304c9b
11 changed files with 514 additions and 7 deletions

23
BuildApiDocs.sh Normal file
View File

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

11
License.md Normal file
View File

@ -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.

View File

@ -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
View File

@ -1,3 +1,102 @@
# SwiftletRadioButtonPicker
# SwiftletRadioButtonPicker for Swift and SwiftUI
A description of this package.
![](https://img.shields.io/badge/license-MIT-green) ![](https://img.shields.io/badge/maintained%3F-Yes-green) ![](https://img.shields.io/badge/swift-5.4-green) ![](https://img.shields.io/badge/iOS-13.0-red) ![](https://img.shields.io/badge/macOS-10.15-red) ![](https://img.shields.io/badge/tvOS-13.0-red) ![](https://img.shields.io/badge/release-v1.0.0-blue)
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:
![](Documentation/Images/Picker01.png)
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:
![](Documentation/Images/Picker02.png)

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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")
}
}

View File

@ -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!")
}
}