[Features] Provides default picker view for @Options with pre-set values

To use, pass in a collection of values to `values` parameter in @Option initializer.
This commit is contained in:
Riley Testut 2023-04-14 18:10:55 -05:00
parent 240b74de94
commit 6d95924145
5 changed files with 113 additions and 2 deletions

View File

@ -167,6 +167,7 @@
BFFDF03F23E3C28A00931B96 /* libGambatte.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFFDF03D23E3C0F000931B96 /* libGambatte.a */; };
BFFDF04623E3D3A600931B96 /* libMupen64Plus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFFDF04523E3D3A600931B96 /* libMupen64Plus.a */; };
D517F6BA29E730DA000D14D0 /* SettingsName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9C01929DDFBDD00A8D610 /* SettingsName.swift */; };
D517F6BE29E7535F000D14D0 /* Collection+Optionals.swift in Sources */ = {isa = PBXBuildFile; fileRef = D517F6BD29E7535F000D14D0 /* Collection+Optionals.swift */; };
D524F4A1273DE9A100D500B2 /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = D524F4A0273DE9A100D500B2 /* AltKit */; };
D524F4A3273DE9C000D500B2 /* ProcessInfo+JIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */; };
D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */; };
@ -193,6 +194,7 @@
D5D7C20229E60F2000663793 /* OptionValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D7C1E629E5F90200663793 /* OptionValue.swift */; };
D5D7C20329E60F2000663793 /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58F39C529E0A473008B4100 /* Option.swift */; };
D5D7C20429E60F2000663793 /* Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9BFFD29DDECF100A8D610 /* Feature.swift */; };
D5D7C20629E60F6100663793 /* OptionPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D592D6FE29E48FFB008D218A /* OptionPickerView.swift */; };
D5D7C20829E616CF00663793 /* FeatureContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D7C20729E616CF00663793 /* FeatureContainer.swift */; };
D5F82FB82981D3AC00B229AF /* LegacySearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */; };
/* End PBXBuildFile section */
@ -407,6 +409,7 @@
BFFDF03D23E3C0F000931B96 /* libGambatte.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libGambatte.a; sourceTree = BUILT_PRODUCTS_DIR; };
BFFDF04523E3D3A600931B96 /* libMupen64Plus.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libMupen64Plus.a; sourceTree = BUILT_PRODUCTS_DIR; };
C786AF1D2DDB6223BE2063CC /* Pods-Delta.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.debug.xcconfig"; sourceTree = "<group>"; };
D517F6BD29E7535F000D14D0 /* Collection+Optionals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Optionals.swift"; sourceTree = "<group>"; };
D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+JIT.swift"; sourceTree = "<group>"; };
D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerManager+Delta.swift"; sourceTree = "<group>"; };
D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Filename.swift"; sourceTree = "<group>"; };
@ -425,6 +428,7 @@
D5864977297756CE0081477E /* CheatBaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatBaseView.swift; sourceTree = "<group>"; };
D58F39C529E0A473008B4100 /* Option.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Option.swift; sourceTree = "<group>"; };
D58F39C829E0A702008B4100 /* UserDefaults+OptionValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+OptionValues.swift"; sourceTree = "<group>"; };
D592D6FE29E48FFB008D218A /* OptionPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionPickerView.swift; sourceTree = "<group>"; };
D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
D5A9BFFD29DDECF100A8D610 /* Feature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feature.swift; sourceTree = "<group>"; };
D5A9C01929DDFBDD00A8D610 /* SettingsName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsName.swift; sourceTree = "<group>"; };
@ -1031,6 +1035,7 @@
children = (
D5A9BFFD29DDECF100A8D610 /* Feature.swift */,
D58F39C529E0A473008B4100 /* Option.swift */,
D5D7C20529E60F4A00663793 /* Views */,
D517F6BB29E737F5000D14D0 /* Types */,
D5D7C1FE29E60EF700663793 /* Protocols */,
D5D7C1F829E60E8600663793 /* Extensions */,
@ -1042,6 +1047,7 @@
isa = PBXGroup;
children = (
D58F39C829E0A702008B4100 /* UserDefaults+OptionValues.swift */,
D517F6BD29E7535F000D14D0 /* Collection+Optionals.swift */,
D54F710329E89DFC009C069A /* NotificationName+Settings.swift */,
);
path = Extensions;
@ -1058,6 +1064,14 @@
path = Protocols;
sourceTree = "<group>";
};
D5D7C20529E60F4A00663793 /* Views */ = {
isa = PBXGroup;
children = (
D592D6FE29E48FFB008D218A /* OptionPickerView.swift */,
);
path = Views;
sourceTree = "<group>";
};
FD1E8AE87FA2DB8793F7B937 /* Pods */ = {
isa = PBXGroup;
children = (
@ -1544,7 +1558,9 @@
D55C468F29E761C000EA6DE9 /* AnyFeature.swift in Sources */,
D5D7C20129E60F2000663793 /* LocalizedOptionValue.swift in Sources */,
D54F710429E89DFC009C069A /* NotificationName+Settings.swift in Sources */,
D517F6BE29E7535F000D14D0 /* Collection+Optionals.swift in Sources */,
D54F710229E89DCB009C069A /* SettingsUserInfoKey.swift in Sources */,
D5D7C20629E60F6100663793 /* OptionPickerView.swift in Sources */,
D5D7C20329E60F2000663793 /* Option.swift in Sources */,
D5D7C20229E60F2000663793 /* OptionValue.swift in Sources */,
D5D7C1F929E60EA500663793 /* UserDefaults+OptionValues.swift in Sources */,

View File

@ -0,0 +1,19 @@
//
// Collection+Optionals.swift
// DeltaFeatures
//
// Created by Riley Testut on 4/12/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import Foundation
extension Collection
{
func appendingNil() -> [Element] where Element: OptionalProtocol, Element.Wrapped: LocalizedOptionValue
{
var values = Array(self)
values.append(Element.none)
return values
}
}

View File

@ -16,6 +16,7 @@ public class Option<Value: OptionValue, DetailView: View>: _AnyOption
public let name: LocalizedStringKey?
public let description: LocalizedStringKey?
public let values: [Value]?
public private(set) var detailView: () -> DetailView? = { nil }
// Assigned to property name.
@ -73,17 +74,24 @@ public class Option<Value: OptionValue, DetailView: View>: _AnyOption
}
}
private init(defaultValue: Value, name: LocalizedStringKey?, description: LocalizedStringKey?)
private init(defaultValue: Value, name: LocalizedStringKey?, description: LocalizedStringKey?, values: (some Collection<Value>)?)
{
self.defaultValue = defaultValue
self.name = name
self.description = description
self.values = values.map { Array($0) }
self.detailView = { nil }
}
private convenience init(defaultValue: Value, name: LocalizedStringKey?, description: LocalizedStringKey?)
{
self.init(defaultValue: defaultValue, name: name, description: description, values: [Value]?.none)
}
}
// "Hidden" Option (no name or custom SwiftUI view)
// "Hidden" Option (no name, pre-set values, or custom SwiftUI view)
public extension Option where DetailView == EmptyView
{
// Non-Optional
@ -102,6 +110,45 @@ public extension Option where DetailView == EmptyView
convenience init(wrappedValue: Value) where Value: OptionalProtocol
{
self.init(defaultValue: wrappedValue, name: nil, description: nil)
}
}
// "Picker" Option (User-visible, pre-set options with default picker UI)
public extension Option where Value: LocalizedOptionValue, DetailView == OptionPickerView<Value>
{
// Non-Optional
convenience init(wrappedValue: Value, name: LocalizedStringKey, description: LocalizedStringKey? = nil, values: some Collection<Value>)
{
self.init(defaultValue: wrappedValue, name: name, description: description, values: values)
self.detailView = { [weak self] () -> DetailView? in
guard let self else { return nil }
return OptionPickerView(name: name, options: Array(values), selectedValue: self.valueBinding)
}
}
// Optional, default = nil
convenience init(name: LocalizedStringKey, description: LocalizedStringKey? = nil, values: some Collection<Value>) where Value: OptionalProtocol, Value.Wrapped: LocalizedOptionValue
{
self.init(defaultValue: Value.none, name: name, description: description, values: values)
self.detailView = { [weak self] () -> DetailView? in
guard let self else { return nil }
return OptionPickerView(name: name, options: values.appendingNil(), selectedValue: self.valueBinding)
}
}
// Optional, default = non-nil
convenience init(wrappedValue: Value, name: LocalizedStringKey, description: LocalizedStringKey? = nil, values: some Collection<Value>) where Value: OptionalProtocol, Value.Wrapped: LocalizedOptionValue
{
self.init(defaultValue: wrappedValue, name: name, description: description, values: values)
self.detailView = { [weak self] () -> DetailView? in
guard let self else { return nil }
return OptionPickerView(name: name, options: values.appendingNil(), selectedValue: self.valueBinding)
}
}
}
// "Custom" Option (User-visible, provides SwiftUI view to configure option)
public extension Option where Value: LocalizedOptionValue

View File

@ -19,6 +19,7 @@ public protocol AnyOption<Value>: AnyObject, Identifiable
var key: String { get }
var settingsKey: SettingsName { get }
var values: [Value]? { get }
var detailView: () -> DetailView? { get }
var value: Value { get set }

View File

@ -0,0 +1,28 @@
//
// OptionPickerView.swift
// Delta
//
// Created by Riley Testut on 4/10/23.
// Copyright © 2023 Riley Testut. All rights reserved.
//
import SwiftUI
// Type must be public, but not its properties.
public struct OptionPickerView<Value: LocalizedOptionValue>: View
{
var name: LocalizedStringKey
var options: [Value]
@Binding
var selectedValue: Value
public var body: some View {
Picker(name, selection: $selectedValue) {
ForEach(options, id: \.self) { value in
value.localizedDescription
}
}
.pickerStyle(.inline)
}
}