Tommy D
Tommy D

Reputation: 53

I know using a variable within a variable in Swift is a no no... but

Ok. Newbie here. I was trying to use a variable within a variable and finally did some searching on here and realized it was a no no... but maybe someone can help me think of a different way to do this.

Again, maybe Im thinking incorrectly in handling this so please feel free to laugh, make fun, or rip on me. :)

I have 4 UIButtons

@IBOutlet var westButton: UIButton!
@IBOutlet var northButton: UIButton!
@IBOutlet var southButton: UIButton!
@IBOutlet var eastButton: UIButton!

When one is clicked I am trying to access a class of places to load the next place. For example.

class Place {
    var Name: String = ""
    var northDirection: String = ""
    var southDirection: String = ""
    var eastDirection: String = ""
    var westDirection: String = ""

    init (Name: String, northDirection: String, southDirection: String, eastDirection: String, westDirection: String) {
        self.Name = Name
        self.northDirection = northDirection
        self.southDirection = southDirection
        self.eastDirection = eastDirection
        self.westDirection = westDirection

    }

... ...

        let Home = Place (Name: "Home", northDirection: "North", southDirection: "South", eastDirection: "East", westDirection: "Park")
        let Park = Place (Name: "Park",  northDirection: "North", southDirection: "South", eastDirection: "Home", westDirection: "West")

What I was doing in the IBAction was trying to take the current 'Place' and get the corresponding place from its class. For example starting at 'Home' geting home.westDirection would be Park, but would like to load the westDirection value of Park to the next Title in the button.

I was hoping to use something like getting title of the west button when it is pressed. ( I am trying to set the new title of the button to the .westDirection value in the class of the title of the button. (Example from above, from the place Home, pressing westButton would return Park.

 westButton.setTitle(("\(westButton.currentTitle!).westDirection"), forState: UIControlState.Normal)

Of course the above returns... "Park.westDirection" since its a string. I was playing with it to try to get the variable within a variable.

I am thinking I have to go back to the drawing board, maybe get away from a class, I was just hoping to have the IBAction kind of automatically fill in the values from the class. ( Maybe Im thinking too much like perl ) I dont know. Anyone have any ideas to help me figure this out a different way?

Upvotes: 0

Views: 104

Answers (2)

oisdk
oisdk

Reputation: 10091

In Swift, the strong typing means you've got to approach this pretty differently. First of all, If you've got a type that has within it references to other types, that means recursion in Swift ("variable within a variable"). And, since this case isn't very well suited to recursion, that's going to mean headaches. As an example, to encode something like this map:

 ___ ___
| a | b |
|___|___|
| c | d |
|___|___|

Using recursion would involve this:

class Place { var north, east, south, west: Place? }

var (a, b, c, d) = (Place(), Place(), Place(), Place())

(a.east, a.south) = (b, c)
(b.west, b.south) = (a, d)
(c.north, c.east) = (a, d)
(d.north, d.east) = (b, c)

Now, that gives you the kind of behaviour you want (kind of). If you look for what's south of a, you just use its property:

a.south

And you can add in any other properties you need. (like a name, say). However, being able to access the places via their names would require a dictionary:

let places = ["Home":a, "Work": b, "Gym":c, "Pub":d]

And then you could access the name of the thing itself like this:

places["Home"]?.south?.someProperty

Where someProperty is a property of the class Place that you're interested in. However, as you can see, this is pretty tedious, and cumbersome. There's a lot of repetition. (you have to say a.east = b as well as b.west = a, for instance) A better approach would be to adopt a data structure that represents the map, and then access the things you want via that. Here's what I managed:

struct Map {

  private let matrix: [[String]]
  private let locations: [String:(Int, Int)]
  private let xBounds, yBounds: ClosedInterval<Int>

  init(_ locs: [[String]]) {
    self.matrix = locs
    var dict: [String:(Int, Int)] = [:]
    for (y, row) in locs.enumerate() {
      guard row.endIndex == locs[0].endIndex else { fatalError("All rows must be equal length") }
      for (x, loc) in row.enumerate() {
        dict[loc] = (x, y)
      }
    }
    self.locations = dict
    self.xBounds = ClosedInterval(0, locs[0].endIndex - 1)
    self.yBounds = ClosedInterval(0, locs.endIndex - 1)
  }

  subscript (x: Int, y: Int) -> String? {
    return (xBounds ~= x && yBounds ~= y) ? matrix[y][x] : nil
  }

  subscript(loc: String) -> (Int, Int)? { return locations[loc] }

  enum Direction: Int { case North = 0, East, South, West }

  private func shift(dir: Direction, from: (Int, Int)) -> (Int, Int) {
    switch dir {
    case .North: return (from.0, from.1 - 1)
    case .South: return (from.0, from.1 + 1)
    case .East:  return (from.0 + 1, from.1)
    case .West:  return (from.0 - 1, from.1)
    }
  }

  func adjacentTo(from: String, dir: Direction) -> String? {
    return locations[from].flatMap {
      self[shift(dir, from: $0)]
    }
  }

  func adjacentTo(from: String) -> [String] {
    return (0..<4).flatMap {
      adjacentTo(from, dir: Direction(rawValue: $0)!)
    }
  }
}

It's probably overkill. But it allows you to have pretty much the behaviour you were looking for. Here's how you'd use it:

// To set up, give it a matrix

let map = Map([
  ["Home", "Work"],
  ["Gym" , "Pub" ]
])

Your matrix can be any size, but all of the rows must be the same length. Then, go ahead and use its methods:

// Find a place in a given direction
map.adjacentTo("Home", dir: .East) // "Work"

// Find all adjacents to a given place
map.adjacentTo("Pub") // ["Work", "Gym"]

Or, on a bigger map:

let map = Map([
  ["Home", "Work"  , "School" ],
  ["Gym" , "Pub"   , "Cafe"   ],
  ["Pool", "Cinema", "Library"]
])

map.adjacentTo("Cinema", dir: .East) // "Library"

map.adjacentTo("Pub") // ["Work", "Cafe", "Cinema", "Gym"]

// Using the coordinate system
map["Home"] // (0, 0)
map[ 1, 1 ] // "Pub"

Upvotes: 1

FabKremer
FabKremer

Reputation: 2179

I think the simplest way is:

  • In the viewDidLoad(), load all the places you have (i.e. Home and Park), and store it them in an array (so array will be of type [Place]).

  • In your button @IBAction call (and before this implement) a custom method called something like findByPlaceName, that given a name it will find and return the Place on the array that has that name.

  • Change the buttons titles from that Place returned.

I think that's what you've been asking, right? Hope it helped!

Upvotes: 0

Related Questions