Duck
Duck

Reputation: 35963

Encoding a struct that has @State

I have a struct like this

struct SymbolView: View, Identifiable {
  let id = UUID()
  
  var name:String
  var code:String
  @State var position = CGPoint(x:100, y:100)
  @State var scale = CGFloat(1)
} 

I need to transform that into JSON by using Codable, so I do

extension SymbolView: Codable{
  private enum CodingKeys: String, CodingKey {
    case name
    case code
    case position
    case scale
  }
  
  public func encode(to encoder:Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    do {
      try container.encode(self.name, forKey: .name)
      try container.encode(self.code, forKey: .code)
      try container.encode(self.position, forKey: .position)
      try container.encode(self.scale, forKey: .scale)
    } catch (let error) {
      print(error.localizedDescription)
    }
  }
}

errors: Cannot automatically synthesize 'Decodable' because 'State' does not conform to 'Decodable' and the same error for the point.

The problem here appears to be that both properties are @State.

How do I solve that?

Upvotes: 1

Views: 400

Answers (1)

jnpdx
jnpdx

Reputation: 52426

I'd suggest encapsulating id, name, code, position, scale into their own encodable struct and use that as a single @State property on SymbolView. Then, just encode/decode the model rather than trying to encode/decode the View.

struct SymbolModel : Identifiable, Codable {
    var id = UUID()
    var name:String
    var code:String
    var position = CGPoint(x:100, y:100)
    var scale = CGFloat(1)
}

struct SymbolView: View {
    @State var symbol : SymbolModel
    
    var body: some View {
        Text("Symbol...")
    }
    
    func encodeSymbolModel() -> Data? {
        try? JSONEncoder().encode(symbol)
    }
}

struct ContentView: View {
    @State var symbolModel : SymbolModel?
    
    var body: some View {
        if let symbolModel = symbolModel {
            SymbolView(symbol: symbolModel)
        }
    }
    
    func decodeSymbolFromData(data: Data) -> SymbolModel? {
        do {
            return try JSONDecoder().decode(SymbolModel.self, from: data)
        } catch {
            print(error)
        }
        return nil
    }
}

Or, keep your original code and write a custom init(from decoder):

extension SymbolView: Codable {
    private enum CodingKeys: String, CodingKey {
        case name
        case code
        case position
        case scale
    }
    
    public func encode(to encoder:Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        do {
            try container.encode(self.name, forKey: .name)
            try container.encode(self.code, forKey: .code)
            try container.encode(self.position, forKey: .position)
            try container.encode(self.scale, forKey: .scale)
        } catch (let error) {
            print(error.localizedDescription)
        }
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        code = try values.decode(String.self, forKey: .code)
        position = try values.decode(CGPoint.self, forKey: .position)
        scale = try values.decode(CGFloat.self, forKey: .scale)
    }
}

Upvotes: 2

Related Questions