3

I have a variable myValue in a model MyModel. In this example I want myValue to only ever be one of 4 values (50 values in real life, in case it affects the answer). Let's say I want myValue to be one of the values from this array: let myArray = [4, 2, 7, 5]

I would like a Slider to be able to change myValue. However the slider only accepts Double as a type.

I'm trying to work around this but so far have only come up with a somewhat convoluted solution...

// TYPE
enum MyType: Int, CaseIterable, Codable {
    case zero = 4
    case one = 2
    case two = 7
    case three = 5
    
    var asIndex : Double {
        switch self {
        case .zero: return 0
        case .one: return 1
        case .two: return 2
        case .three: return 3
        }
    }
}

// MODEL
struct MyModel: Codable {
    var myValue: MyType = .zero
}

// VIEWS
struct MyView: View {
    @Binding var myModel: MyModel

    var body: some View {
        
        Slider(value: Binding.init(
            get: { () -> Double in return myModel.myValue.asIndex },
                set: { newValue in
                    
                    if let unwrappedMyType = MyType(rawValue: Int(newValue)) { myModel = MyModel(myValue: unwrappedMyType) } }
               
        ), in: 0...3)
        
        Text("\(myModel.myValue.rawValue)" as String) // 4, 2, 7 or 5 depending on the slider being set to values 0,1,2 or 3
    }
}

struct ContentView: View {
    @State var myModel: MyModel = MyModel()
    
    var body: some View {
        MyView(myModel: $myModel)
            .frame(width:300, height: 100)
    }
}

It's almost working but the slider is resisting being set. What am I doing wrong? Is there a simpler or better way to do this?

Ribena
  • 1,086
  • 1
  • 11
  • 20

2 Answers2

3

By default Slider is continuous in range, so important part for mapping to enum is to give it a step.

Here is possible solution. Tested with Xcode 12.4 / iOS 14.4

demo

Below are modified parts only:

enum MyType: Int, CaseIterable, Codable {
    case zero = 4
    case one = 2
    case two = 7
    case three = 5

    init(index: Double = 0) {
        if let type = Self.allCases.first(where: { $0.asIndex == index }) {
            self = type
        } else {
            self = .zero
        }
    }

    var asIndex : Double {
        switch self {
        case .zero: return 0
        case .one: return 1
        case .two: return 2
        case .three: return 3
        }
    }
}

// VIEWS
struct MyView: View {
    @Binding var myModel: MyModel

    var body: some View {

        Slider(value: Binding(
            get: { myModel.myValue.asIndex },
            set: { myModel.myValue = MyType(index: $0) }
        ), in: 0...3, step: 1)                             // give step !!

        Text("\(myModel.myValue.rawValue)" as String) // 4, 2, 7 or 5 depending on the slider being set to values 0,1,2 or 3
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
1

Here's another technique using a variable to convert between enum and Double used in slider. (Inspired by this )

    enum Custom: Int, CaseIterable {
        case one = 1,
        two  = 2,
        three = 3
    }
    
    
    struct EnumSlider: View {
        @Binding  var custom: Custom
        
        // convert between enum and double binding
        var enumProxy: Binding<Double>{
            Binding<Double>(get: {
                //returns the enum as a Double
                return Double(custom.rawValue)
            }, set: {
                //Cast dobule as int and use as enum's raw value
                custom = Custom(rawValue: Int($0)) ?? .one
            })
        }
        
        var body: some View {
            VStack(){
                Slider(value: enumProxy, in: 1...3, onEditingChanged: {_ in })
                Text("\(custom.rawValue)")
            }
            
        }
    }
awct
  • 125
  • 1
  • 5