Reputation: 53
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
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
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