SwiftUI - using all but one values of Enum for different Pickers - swift

In my app, I need to use a Picker in two different places. In one place, it's to set a property to one of three values: A, B, or C. But, in another place, I want to use the picker to filter a array of items to show either all items (no filter) or only the A, B, or C items.
So, my first attempt was to create an enum like so:
enum Category {
case all
case A
case B
case C
}
Using this, I can filter my list successfully. But, when I try to create a new item in the array, I don't want all to be an option. The user should only see a picker with A, B, and C.
I could try:
enum Category {
case A
case B
case C
}
but then how do I add the all option for the filter picker?
(To address some comments below, here are some more details on the specific thing I'm try to accomplish...
A tournament can be a men's, women's, or mixed tournament. On the screen where I list the tournaments, I want to be able to show all tournaments or just the men's, just the women's or just the mixed tournaments. So, I have a picker that spans the width of the iPhone. Looks and works great.
Obviously, when adding a new item to the list, I have to specify its category, but not "all". Also a picker.
So, in one case, I need three values, in the other case, I need the same three values with "All" added at the beginning.)

You should define your enum without the all case, because all is not a valid Category for an item. (This is a programming guideline known as “make illegal states unrepresentable”.)
enum Category: Hashable, CaseIterable {
case a
case b
case c
}
With that definition, the Picker for setting an item's property can look like this:
Picker("Category", selection: $category) {
ForEach(Category.allCases, id: \.self) { category in
Text(verbatim: "\(category)")
.tag(category)
}
}
Then you should recognize that your filter is optional. You can filter by item category, or you can perform no filtering. So your filter property should be declared optional:
#Binding var categoryFilter: Category?
The Picker for setting the filter then needs to be careful to use optional tags:
Picker("Category Filter", selection: $categoryFilter) {
Text("None")
.tag(Category?.none)
ForEach(Category.allCases, id: \.self) { category in
Text(verbatim: "\(category)")
.tag(Category?.some(category))
}
}

You can create enum in this way
enum Category {
case all
case A
case B
case C
static let categories: [Category] = [.A, .B, .C]
}
And then use the categories array when you need to choose among three.

You can make your enum CaseIterable and then you can use the allCases property when you want to display all enum values, and use a filter when you only want certain cases displayed.
NOTE: In my sample code, you'll need to replace selection: .constant(Category.a) with an #Binding
struct Test {
enum Category: String, CaseIterable {
case all = "All"
case a = "A"
case b = "B"
case c = "C"
}
struct TestView: View {
var body: some View {
VStack {
Picker("Picker Title", selection: .constant(Category.a)) {
ForEach(Category.allCases, id: \.self) { category in
Text(category.rawValue).tag(category)
}
}
Picker("Picker Title", selection: .constant(Category.a)) {
ForEach(Category.allCases.filter({ $0 != .all }), id: \.self) { category in
Text(category.rawValue).tag(category)
}
}
}
}
}
}

To answer your question if I understood you correctly.
Modify your enum to implement the CaseIterable take a look at it below.
enum Category:CaseIterable{
case all
case A
case B
case C
}
Filter out where your category matches the all case. Example below.
let preferredcategory = Category.allCases.filter{ return $0 != Category.all}
To test our result see the code below.
print(preferredcategory.count)
for catname in preferredcategory {
print("Hello, \(catname)!")
}
You can read more on this respective pages.
https://developer.apple.com/documentation/swift/caseiterable
https://developer.apple.com/documentation/swift/caseiterable/2994869-allcases
https://www.hackingwithswift.com/example-code/language/how-to-list-all-cases-in-an-enum-using-caseiterable
Thanks.
So you can use the preferredCategory variable to show the user your category for the one you do not want all to show. you can also use the same filter to show only the part of the enum that has all case as well.
Also, the good thing is you get to keep your enum and reuse it everywhere in your application without having to hardcode values. if you want to show only All you just have to filter out the remaining case.
Some people may want to downvote without a valid reasons but if you believe the approach is wrong feel free to edit or leave a comment on how to improve the solution.

Related

SwiftUI: How could I use polymorphism to make a model class create its own View

I am trying to build a small App in which you can create tests with questions using SwifUI, but finding how to make everything works out is getting hard for a newbie like me. The app would show a list of questions in a main scrollable view and these questions could be of different types such as true or false, text, multiple choice, etc… and could be active or not.
I thought it would be great that all different types of questions adopted the same protocol. This protocol would also define a function or a computed property in charge of display its on view using the values store in the different attributes. However, the problem comes up when trying to modify any of this parameters interacting with that View. Let's say I want to add a toggle button that active or reactive the question, modifying one of the values of that question. With the different solutions I implemented, I didn't get the view being rebuild/updated.
I tried several things to accomplish this, like wrapping those properties that are supposed to update their values with #State or #Binding. I also tried to turn those properties into ObservableObjects, adding new classes that adopt the ObservableObject protocol, but it does not work. The only thing that seems to work is, for any type of Question, create a view, with an observable ViewModel. Later, in the view where I display all the question, I have to create a Switch with all the different possibilities.
What I don't like about this solutions is that if I wanted to add a new type of question, I would have to modify this main view to include an extra case for this new type of question, what is against the Open-Closed principle.
Do you have any suggestion guys to assign this responsibility to any question class instead of to the main view?
Thank you in advance :)
Usually you don't want a model to know about the view. That's backwards. But yet we often want to hide the concrete selection logic behind a single call that does different things depending on the data provided (similar to polymorphism via function overloading).
The key idea is the use of the Factory Pattern combined with the Visitor Pattern to keep in harmony with the Open-Closed Principle.
When we do this kind of thing with regular objects, we often use a factory method to return the proper subclass for the input data. Inside that factory method usually sits a switch statement. The factory interface lets us honor the Open-Closed principle so that ContentView doesn't change when we add new question types. Chances are, what follows is probably very similar to what you had yourself with the view model approach.
In SwiftUI, the best approach at a factory-style view would be to create a QuestionView that then knows how to create the correct concrete view for each question object. I hope that default + fatalError() makes you consider how an enum might be useful here.
struct QuestionView: View {
let question: Question
var body: some View {
switch question {
case let q as TextQuestion:
TextQuestionView(question: q)
case let q as DoubleQuestion:
DoubleQuestionView(question: q)
default:
fatalError("Unknown question type")
}
}
}
Then in your main view it would be polymorphic, reacting dynamically to the actual Question instances. You'd use it something like this:
struct ContentView: View {
#State private(set) var questions: [Question]
var body: some View {
NavigationView {
Form {
ForEach(questions, id: \.key) { question in
QuestionView(question: question)
}
Button("Submit") {
let answers = Answers()
for question in questions {
question.record(answers: answers)
}
print(answers)
}
}
.navigationTitle("Questions")
}
}
}
A reasonable way to extract the answers is to use the Visitor Pattern in a way similar in structure to Encodable's encode(to encoder: Encoder). We expect each specialty view to communicate with its specific Question object, and then expect the Question object to contain an implementation of func record(answers: Answers). When it's time, loop through questions and tell them to record their answers. (Note that we can add various Answers implementations without changing the Question subclasses, in keeping with the Open-Closed principle).
The Question objects are like view models, and they are ObservableObjects. You can see how they record their answers when asked.
For this to work, they cannot be protocols with associated types. That just kills using them in an array.
class TextQuestion: Question, ObservableObject {
#Published var answer = ""
override func record(answers: Answers) {
answers.addAnswer(key: key, value: answer)
}
}
class MeasurementQuestion: Question, ObservableObject {
let unit: String
#Published var answer = 0.0
init(key: String, question: String, unit: String) {
self.unit = unit
super.init(key: key, question: question)
}
override func record(answers: Answers) {
answers.addAnswer(key: key, value: answer)
}
}
Then each individual question subtype view will watch its own Question instance:
struct TextQuestionView: View {
#ObservedObject private(set) var question: TextQuestion
var body: some View {
Section(question.question) {
TextField("Answer", text: $question.answer)
}
}
}
struct MeasurementQuestionView: View {
#ObservedObject private(set) var question: MeasurementQuestion
var body: some View {
Section(question.question) {
HStack {
TextField("Answer", value: $question.answer, format: .number)
Text(question.unit)
}
}
}
}
You can simply add your list of questions to the preview and see how it works:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(questions: Example.questions)
}
}
struct Example {
static let questions: [Question] = [
TextQuestion(
key: "name",
question: "What...is your name?"
),
TextQuestion(
key: "quest",
question: "What...is your quest?"
),
[
TextQuestion(
key: "assyria",
question: "What...is the capital of Assyria?"
),
TextQuestion(
key: "color",
question: "What...is your favorite colour?"
),
MeasurementQuestion(
key: "swallow",
question: "What is the air-speed velocity of an unladen swallow?",
unit: "kph"
)
].randomElement()!
]
}
I'm not sure I like this implementation for anything beyond a toy project—I prefer stronger layer separations. This does, however, setup a polymorphic view that adjusts to data implementation types. The key idea is the use of the Factory Pattern combined with the Visitor Pattern.

Swift Variables and Enums in ForEach Loop for Bindings

I have a ForEach loop in which I am displaying X amount of fields over and over again depending on user entry (i.e. they enter 5, it displays 5 iterations of these fields).
I need to be able to set the TextField, FocusState, and AccessibilityFocusState bindings for these fields dynamically. I know I have to conform the enum to CaseIterable but I can't separately define an infinite number of enum cases just in case a user enters a value outside of that in which I have configured enum cases.
I also realize that in my example I have only defined a single #State variable, but as this goes along with the question, how do you define X amount of variables when doing something like this?
Here's an example:
struct TestView: View {
#AccessibilityFocusState var accessFocus: AccessFocusField?
#FocusState var isFocused: Field?
#State private var field = ""
enum AccessFocusField: CaseIterable {
case fieldName
}
enum Field: CaseIterable {
case fieldName
}
var body: some View {
ForEach(1...5) { value in
TextField("Hello World!", $field[value])
.focused($isFocused, equals: .fieldName[value]
.accessibilityFocused($accessFocus, equals: .fieldName[value])
}
}
}
For ForEach I would create an identifiable object, named Field with all the properties that Textfield needs. And then declare an array of that object, and use that in ForEach.

How to get all values from nested enums?

Working with my key/value Strings file, I'm looking for a simpler way of pulling out values by passing an enum into a function, which does the lookup and returns that value.
This Strings file can be updated over the network, so my app ships with a Default version. Currently, to get those defaults into memory I iterate over my enums cases and add the strings to a dictionary.
It looks like this:
enum StringKeys: String, CaseIterable {
case string1
case string2
case string3
}
func setupDefaults() {
for key in StringKeys.allCases {
defaults[key] = stringFile.value(forKey: key)
}
}
func getValue(for: StringKeys) -> String {
// stuff here...
}
This has all been fine for a while now, however this enum is getting quite large and so autocomplete is less helpful.
What I would like to have is something more like this:
enum StringKeys: CaseIterable {
enum Feature1: String, CaseIterable {
case title1
case button1
}
enum Feature2: String, CaseIterable {
case title3
case toggle1
}
}
Then call my getValue function with:
titleLabel.text = Strings.getValue(for: StringKeys.Feature1.title1)
This is all totally fine too, but I can't work out how to iterate over these nested keys to build the defaults dictionary in a nice clean way.
I tried extending CaseIterable to add a little allCasesRecursive function. Which Swift wouldn't allow.
I know this can be done by adding a property inside each enum, returning allCases, and combining them together at each parent. But that's still a bit messy.
The other option is to not use the enums to set up defaults, and instead just parse the whole file and put that directly into defaults, but I'd much rather the enums decide what goes in the defaults. This also makes it easy to see if any are missing i.e, the default for that key won’t be found, and I can throw an error.
So, basically I want to turn a bunch of nested enums into a flat dictionary. Even just an array of keys would be enough to get me the rest of the way. Is it possible to recursively drill down through nested enums? Could I use Reflection to solve this problem?
I'm also open to other ideas, otherwise I'll just stick with the giant, flat enum.
I don't know if i understand your question correctly, maybe this will help
// Create a protocol
protocol StringKeyValue {}
enum StringKeys: CaseIterable {
enum Feature1: String, CaseIterable, StringKeyValue {
case title1
case button1
}
enum Feature2: String, CaseIterable, StringKeyValue {
case title3
case toggle1
}
}
func getValue(for: StringKeyValue) -> String {
return "hi there"
}
// Maybe this's what you want?
titleLabel.text = getValue(for: StringKeys.Feature1.title1)

Enumeration of Enumerations and avoiding the use of Any type

I'm building a model for an app that tracks data related to wedding dress alterations. Our model diagram shows related enumerations, with "Section" being the outermost enumeration, and then each case connects to an additional, more detailed enumeration.
When the Alteration Specialist is doing the fitting, they will be entering which alterations they plan to do. I need an efficient way to track the relationships between these quantities.
See object model image
I have implemented a basic example, but I'm having trouble storing an array of these "Alteration" objects and defining the appropriate type.
Here is my Playground code:
struct Alteration {
var detail: Any
enum section {
case bodice
enum Bodice {
case addBraCups
case addOrRemoveBoning
}
case sides
enum Sides {
case bustTakeInOrOut
case hipOrWaistTakeInOrOut
}
}
}
var alterationOne = Alteration(detail: Alteration.section.Bodice.addBraCups)
var alterationTwo = Alteration(detail: Alteration.section.Sides.bustTakeInOrOut)
var alterations = [Any]()
alterations.append(alterationOne)
alterations.append(alterationTwo)
This code compiles but I would prefer to not use Any. I'm also wondering if it makes sense to nest enumerations in this manner and if these nested enumerations make sense inside of a struct or class.
Try using enums with associated values. Your structs could look like this:
enum BodiceVariation {
case addBraCups
case addOrRemoveBoning
}
enum SidesVariation {
case bustTakeInOrOut
case hipOrWaistTakeInOrOut
}
enum Alteration {
case bodice(BodiceVariation)
case sides(SidesVariation)
}
let alterationOne = Alteration.bodice(.addBraCups)
let alterationTwo = Alteration.sides(.bustTakeInOrOut)
var alterations = [Alteration]()
alterations.append(alterationOne)
alterations.append(alterationTwo)
(assuming that each of the alterations you've listed are mutually exclusive)
One problem with enums is that they are for things that are mutually exclusive. But yours are not.
I think I would use arrays of alteration types and use classes and subclass to specify what each one is. For example:
struct Dress {
var bodiceAlterations = [BodiceAlteration]()
var sideAlterations = [SideAlteration]()
}
enum InOrOut {
case `in`
case out
}
enum AddOrRemove {
case add
case remove
}
class BodiceAlteration {}
class BraCupAlteration : BodiceAlteration {
}
class BoningAlteration : BodiceAlteration {
var addOrRemove = AddOrRemove.add
}
class SideAlteration {}
class BustAlteration : SideAlteration {
var inOrOut = InOrOut.out
}
class HipAlteration : SideAlteration {
var inOrOut = InOrOut.out
}
An array can list multiple alterations of its type, and an array can happily be empty if there are no alterations of that type. Then the subclass tells you the more specific type. The enums are just for mutually exclusive choice: a hip alteration can be in or out but not both.
The classes give you room to grow, e.g. add measurements and other data as properties, as appropriate. If you have lots of classes that have inch measurements, dates, etc., you could use a protocol to unite them if you needed to (though if they all have that you can just inherit from the superclass).

Choosing between strcuts, classes and enums

I have some sample code I put together as I am trying to figure out how to model my first app. I'm not sure if the "shoe examples" should be enums or classes. I guess I could create one class that has all the features that each class has ( sole, string, color, fit) and then make subclasses of the main class and add the other features for classes that need them. I also thought about setting each up as enums because the data is fixed. Ultimately I want a user to be able to push a button and decide if a condition is a poor, fair, or excellent for each case in the enum or each stored property in the class or struct.
enum Conditions {
case poor
case fair
case excellent
}
// Shoe exmaples
enum Nike {
case sole
case string
case color
case fit
}
struct Rebook {
case sole
case string
case color
case fit
}
enum Jordan {
case sole
case string
case color
case fit
}
enum sketchers {
case sole
case string
case color
case fit
case (idk something else)
}
enum heels {
case sole
case string
case color
case fit
case height
case width
}
or
Your current approach is almost there. If I understand you correctly, you want each property of the shoe to be ranked with a Condition enum. You might have more luck making each shoe a generic class, like so:
class Shoe {
var sole: Condition?
var string: Condition?
var color: Condition?
var fit: Condition?
init(sole: Condition, string: Condition, color: Condition, fit: Condition) {
self.sole = sole
self.string = string
self.color = color
self.fit = fit
}
}
This will now allow you to create instances of Shoe for every type you want, instead of a different enum for each shoe. For example, if the user ranked a Nike, you might store their results like so:
var Nike = Shoe(sole: .poor, string: .excellent, color: .fair, fit: .excellent)
If you want to add different rankings for each shoe, such as "idk something else" or "height", then you can always add more properties to the Shoe class and make them optional, or you can subclass the Shoe class to add more properties.