Using Delegates in Swift: An Example with Pokemon

Delegation is a powerful design pattern in Swift that allows one object to communicate with another object in a loosely-coupled way. This means that the object sending the message (the delegator) doesn’t need to know anything about the object receiving the message (the delegate), other than that it conforms to a certain protocol.

In this example, we’ll use the delegation pattern to control whether a Pokemon can use its attack. We’ll create a Pokemon struct with a name and a power property, and a Trainer class that has a delegate property and a pokemon property. The delegate property will be of type PokemonAttackDelegate, a protocol we’ll define later.

struct Pokemon {
    let name: String
    let power: Int
}

protocol PokemonAttackDelegate {
    func canUseAttack(_ pokemon: Pokemon) -> Bool
}

class Trainer {
    var delegate: PokemonAttackDelegate?
    var pokemon: Pokemon
    
    init(pokemon: Pokemon) {
        self.pokemon = pokemon
    }
    
    func useAttack() {
        if let delegate = delegate {
            if delegate.canUseAttack(pokemon) {
                print("\(pokemon.name) used its attack with power \(pokemon.power)!")
            } else {
                print("\(pokemon.name) can't use its attack.")
            }
        } else {
            print("No delegate set.")
        }
    }
}

In the useAttack method of the Trainer class, we check if the delegate property is not nil, and if it’s not, we call its canUseAttack method, passing in the pokemon property. If the delegate says that the attack can be used, we print a message to the console. Otherwise, we print a different message.

Now let’s define the PokemonAttackDelegate protocol:

protocol PokemonAttackDelegate {
    func canUseAttack(_ pokemon: Pokemon) -> Bool
}

The only requirement of this protocol is a single method called canUseAttack, which takes a Pokemon parameter and returns a boolean indicating whether the attack can be used or not. We’ll implement this protocol in our ViewController class, which we’ll set as the delegate of the Trainer instance.

class ViewController: UIViewController, PokemonAttackDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let pikachu = Pokemon(name: "Pikachu", power: 50)
        let trainer = Trainer(pokemon: pikachu)
        trainer.delegate = self
        trainer.useAttack()
    }
    
    func canUseAttack(_ pokemon: Pokemon) -> Bool {
        // Let's say that Pikachu can only use its attack if its power is greater than 40
        return pokemon.power > 40
    }
}

In our ViewController class, we conform to the PokemonAttackDelegate protocol and implement the canUseAttack method. In this example, we say that Pikachu can only use its attack if its power property is greater than 40. We then create a Trainer instance with a Pikachu Pokemon instance and set the delegate property to self, which is an instance of ViewController. Finally, we call the useAttack method on the Trainer instance, which will call the canUseAttack method on the delegate (ViewController) to determine if the attack can be used.

In conclusion, delegates are a powerful tool in Swift that allow for flexible communication between objects. By implementing the delegate pattern, we can create reusable code that can be adapted to various use cases. In this example, we saw how delegates can be used in a Pokemon-themed scenario, where a Trainer object can use its Pokemon’s attack only if certain conditions are met. By understanding how to implement and use delegates in our own projects, we can make our code more modular and easier to maintain.