Alex Daro
Alex Daro

Reputation: 69

In Swift, can one use a string to access a struct property?

I have a struct and I would like to know if I can access variables using bracket syntax. Here is my struct:

import UIKit

public struct Pixel {
    public var value: UInt32
    public var red: UInt8
    public var green: UInt8
    public var blue: UInt8
    public var alpha: UInt8
}

public struct RGBAImage {
    public var pixels: [ImageProcessor_Sources.Pixel]
    public var width: Int
    public var height: Int
    public init?(image: UIImage)
    public func toUIImage() -> UIImage?
}

I would like to access the variable like so pixel["red"], as opposed to pixel.red

var image = RGBAImage(image: image!)!
var pixel = image.pixels[index]
pixel["red"] = 255 // as opposed to pixel.red

Is there any way to do this in Swift?

Upvotes: 4

Views: 5411

Answers (4)

jscs
jscs

Reputation: 64002

I don't think string-based access like this is good Swift style. vadian shows how it can be done, but to dynamically get and set members like this, it would be best to use the built-in keypath functionality:

let redChannel = pixel[keyPath: \.red]
pixel[keyPath: \.green] = 0xB5

Another option (more relevant prior to Swift 4) would be to use an enum to define the keys:

enum Component
{
    case red
    case green
    case blue
    case alpha
}

Then adapt the subscript function that vadian demonstrated to accept Pixel.Component instead of String.

This has a significant advantage in that you can no longer pass an invalid key.

Based on your definition:

public extension Pixel
{
    public enum Component
    {
        case red, blue, green, alpha
    }

    public subscript(key: Component) -> UInt8
    {
        get
        {
            switch key {
                case .red: return self.red
                case .green: return self.green
                case .blue: return self.blue
                case .alpha: return self.alpha
            }
        }
        set
        {
            switch key {
                case .red: self.red = newValue
                case .green: self.green = newValue
                case .blue: self.blue = newValue
                case .alpha: self.alpha = newValue
            }
        }
    }
}

var pxl = Pixel(value: 0xFEEDFACE, red: 0xFE, green: 0xED, blue: 0xFA, alpha: 0xCE)
let redChannel = pxl[.red]
print(redChannel)
pxl[.green] = 0xB5
print(pxl)

Upvotes: 7

Charlton Provatas
Charlton Provatas

Reputation: 2274

If OP is looking for a cleaner alternative, there is a pod that extends KVC to native Swift structs:

https://github.com/bradhilton/SwiftKVC

Upvotes: 0

matt
matt

Reputation: 534950

What you're describing is not a Swift feature, unless you use subscripting (as vadian has explained). If you want something that supplies subscripting automatically, use a dictionary. Otherwise, if you really want true introspection, use Cocoa — use a class derived from NSObject instead of a struct, and employ key-value coding.

Upvotes: 3

vadian
vadian

Reputation: 285069

Just for fun and to show that it is possible, but it's ugly

public struct Pixel {
  public var value: UInt32
  public var red: UInt8
  public var green: UInt8
  public var blue: UInt8
  public var alpha: UInt8

  subscript(key: String) -> UInt8 {
    get {
      switch key {
      case "red": return self.red
      case "green": return self.green
      case "blue": return self.blue
      case "alpha": return self.alpha
      default: fatalError("Invalid key")
      }
    }
    set {
      switch key {
      case "red": self.red = newValue
      case "green": self.green = newValue
      case "blue": self.blue = newValue
      case "alpha": self.alpha = newValue
      default: fatalError("Invalid key")
      }
    }
  }
}

Upvotes: 3

Related Questions