Percursos
/
Swift
Swift
/
Exercícios
/
Sistema de janelas
Sistema de janelas

Sistema de janelas

Exercício de aprendizagem

Introdução

Estruturas e classes

As estruturas e as classes são dois dos principais blocos de construção da programação em Swift. Ambas são formas de agrupar dados e funções relacionados em unidades de funcionalidade autónomas. E quando defines uma estrutura ou uma classe, estás a definir um novo tipo para usar em Swift, tal como usaste os tipos com que já trabalhaste, como Int e String.

Há muitas semelhanças entre estruturas e classes em Swift. Entre outras semelhanças, ambas conseguem armazenar valores em propriedades e fornecer funcionalidade através do uso de métodos.

Cada uma delas oferece também algumas funcionalidades adicionais, que estão fora do âmbito deste exercício.

Definir estruturas e classes

Tanto as estruturas como as classes são definidas de forma semelhante. Começam com a palavra-chave adequada, seguida do nome do tipo que está a ser definido; a seguir vem o corpo da estrutura ou da classe, colocado entre chavetas.

O corpo pode ser composto por propriedades armazenadas, que são definidas e se comportam exatamente como constantes ou variáveis normais.

struct CharacterStats {
  var health = 0.0
  var speed = 0
  var strength = 0
}

class GameCharacter {
  var stats = CharacterStats()
  var characterClass: String?
  var name: String?
  var active = false
  let id = makeRandomID()
}

Instâncias

Como vimos acima, definir uma estrutura ou uma classe é apenas definir um novo tipo. É só o plano do que serão os valores desse tipo, mas não cria, na verdade, nenhum valor desse tipo com que possas trabalhar.

Para criar uma instância desse tipo, tens de escrever o nome do tipo seguido de um par de parênteses.

let someStats = CharacterStats()
let someCharacter = GameCharacter()

Isto cria valores desses tipos, em que as propriedades são preenchidas com os valores predefinidos fornecidos na definição. Repara que, em casos opcionais como a propriedade name de GameCharacter, se não for fornecido um valor, a propriedade assume nil por omissão, tal como acontece ao definir tipos opcionais normais em que não se fornece logo um valor.

No caso das estruturas, o Swift fornece automaticamente algo chamado inicializador membro a membro, em que os valores das propriedades da estrutura podem ser indicados dentro dos parênteses e substituem os valores predefinidos da definição.

let differentStats = CharacterStats(health: 100.0, speed: 6, strength: 18)

Aceder às propriedades

As propriedades das estruturas e das classes podem ser acedidas com a notação de ponto, em que o nome do valor é seguido de um ponto (.) e do nome da propriedade. Se uma propriedade de uma estrutura ou classe tiver propriedades próprias, também podes usar esta notação de ponto para aceder a essas propriedades aninhadas.

Esta notação pode ser usada tanto para obter o valor da propriedade como, quando permitido, para o alterar.

someStats.health
// => 0
someCharacter.name
// => nil

someStats.health = 87.3
someStats.health
// => 87.3
someCharacter.name = "Luther"
someCharacter.name
// => "Luther"

someCharacter.id  = "new id"
// Error: Cannot assign to property: 'id' is a 'let' constant

Métodos

Tal como as propriedades, que guardam dados nas tuas estruturas e classes, também podes definir métodos, que guardam funções na tua estrutura ou classe.

Os métodos são definidos da mesma forma que uma função normal, mas dentro do corpo da estrutura ou da classe. Repara que, se uma função alterar o valor de uma propriedade de uma estrutura, tem de ser precedida da palavra-chave mutating. Além disso, se uma propriedade puder ser alterada por um método, essa propriedade tem de ser definida com var e não com let, tal como as variáveis normais.

struct CharacterStats {
  var health = 0.0
  var speed = 0
  var strength = 0

  mutating func takeHit(_ damage: Double) {
    health = max(0.0, health - damage)
  }
  func canLift(_ weight: Int) -> Bool {
    weight < strength * 100
  }
}

class GameCharacter {
  var stats = CharacterStats()
  var characterClass: String?
  var name: String?
  var active = false
  let id: String = makeRandomID()

  func takesDamage(_ damage: Double) {
    stats.takeHit(damage)
    if stats.health <= 0 {
      active = false
    }
  }
  func sayName() -> String {
    return "My name is \(name ?? "no one"), my class is \(characterClass ?? "undetermined")"
  }
  func lift(_ weight: Int) -> String {
    if stats.canLift(weight) {
      return "No problem!"
    } else {
      return "Ooof! No way."
    }
  }
}

Estes métodos podem ser chamados com a notação de ponto, tal como as propriedades.

var myChar = GameCharacter()
myChar.stats = CharacterStats(health: 72.8, speed: 19, strength: 6)
myChar.active = true

myChar.lift(750)
// => "Ooof! No way."
myChar.takesDamage(80)
myChar.active
// => false

Self

Cada instância de uma estrutura ou classe tem uma propriedade implícita chamada self, que se refere à própria instância. A utilização mais comum de self é distinguir entre uma propriedade da instância e um parâmetro com o mesmo nome:

struct Position {
    var x = 0

    mutating func move(x: Int) {
        // Here, 'self.x' refers to the struct's property,
        // while 'x' refers to the parameter passed to the method.
        self.x = x
    }
}

Os tipos de Swift também disponibilizam a palavra-chave Self com maiúscula inicial, que se refere ao tipo da instância e não à própria instância.

Instruções

Neste exercício, vais simular um sistema informático baseado em janelas. Vais criar janelas que podem ser movidas e redimensionadas e que mostram o seu conteúdo. A imagem seguinte é representativa dos valores com que vais trabalhar abaixo.

                  <--------------------- screenSize.width --------------------->

       ^          ╔════════════════════════════════════════════════════════════╗
       |          ║                                                            ║
       |          ║          position.x,_                                      ║
       |          ║          position.y  \                                     ║
       |          ║                       \<----- size.width ----->            ║
       |          ║                 ^      *──────────────────────┐            ║
       |          ║                 |      │        title         │            ║
       |          ║                 |      ├──────────────────────┤            ║
screenSize.height ║                 |      │                      │            ║
       |          ║            size.height │                      │            ║
       |          ║                 |      │       contents       │            ║
       |          ║                 |      │                      │            ║
       |          ║                 |      │                      │            ║
       |          ║                 v      └──────────────────────┘            ║
       |          ║                                                            ║
       |          ║                                                            ║
       v          ╚════════════════════════════════════════════════════════════╝

1. Define uma estrutura Size

Define uma estrutura chamada Size com duas propriedades do tipo Int, width e height, que guardam, respetivamente, a largura e a altura atuais da janela. A largura e a altura iniciais devem ser 80 e 60, respetivamente. Inclui um método resize(newWidth:newHeight:) que recebe novos parâmetros de largura e altura e altera as propriedades de acordo com o novo tamanho.

let size1080x764 = Size(width: 1080, height: 764)
// Returns Size
var size1200x800 = size1080x764
// Returns Size
size1200x800.resize(newWidth: 1200, newHeight: 800)
size1200x800.height
// Returns 800

2. Define uma estrutura Position

Define uma estrutura chamada Position com duas propriedades do tipo Int, x e y, que guardam, respetivamente, a posição horizontal e vertical atual do canto superior esquerdo da janela. Os valores iniciais de x e y devem ser ambos 0. A posição (0, 0) é o canto superior esquerdo do ecrã, com os valores de x a aumentar à medida que te deslocas para a direita e os valores de y a aumentar à medida que te deslocas para baixo.

Inclui um método moveTo(newX:newY:) que recebe novos parâmetros x e y e altera as propriedades de acordo com a nova posição.

var point = Position(x: 10, y: 20)
// Returns Position
point.moveTo(newX: 100, newY: -100)
point.y
// Returns -100

3. Define uma classe Window

Define uma classe de janela com as seguintes propriedades:

  • title : String, valor inicial é "New Window"
  • screenSize : Size, valor constante com width = 800 e height = 600
  • size : Size, valor inicial é o valor predefinido da estrutura Size
  • position : Position, valor inicial é o valor predefinido da estrutura Position
  • contents : String?, valor inicial é nil

Deves também definir um inicializador vazio para a classe.

let window = Window()
window.title
// Returns "New Window"

4. Adiciona um método para redimensionar janelas

  • resize(to:) : (Size) -> () - Este método recebe uma estrutura Size como parâmetro de entrada e tenta redimensionar a janela para o tamanho especificado. No entanto, o novo tamanho não pode exceder certos limites.
  • A altura ou a largura mínima permitida é 1. Alturas ou larguras pedidas inferiores a 1 serão limitadas a 1.
  • A altura e a largura máximas dependem da posição atual da janela; os bordos da janela não podem ultrapassar os bordos do ecrã. Valores superiores a estes limites serão limitados ao maior tamanho possível. Por exemplo, se a posição da janela for x = 400, y = 300 e for pedido um redimensionamento para height = 400, width = 300, a janela será redimensionada para height = 300, width = 300, porque o ecrã não é suficientemente grande na direção y para acomodar totalmente o pedido.

5. Adiciona um método para mover janelas

  • move(to:) : (Position) -> () - É semelhante a resize(to:), mas este método ajusta a posição da janela ao valor pedido, em vez do tamanho. Tal como em resize, a nova posição não pode exceder certos limites.
  • A posição mais pequena é 0, tanto para x como para y.
  • A posição máxima em qualquer das direções depende do tamanho atual da janela; os bordos não podem ultrapassar os bordos do ecrã. Valores superiores a estes limites serão limitados ao maior tamanho possível. Por exemplo, se o tamanho da janela for x = 250, y = 100 e for pedido um movimento para x = 600, y = 200, a janela será movida para x = 550, y = 200, porque o ecrã não é suficientemente grande na direção x para acomodar totalmente o pedido.

6. Adiciona métodos para atualizar o texto da janela e mostrar informações da janela

  • update(title:) : (String) -> () - Este método define a propriedade title com o valor da string que foi passada.
  • update(text:) : (String?) -> () - Este método define a propriedade contents com o valor da string opcional que foi passada.
  • display() : () -> String - Este método devolve uma string que descreve o estado atual da janela. Por exemplo, se a janela tiver o title "My First Window", com a posição: x = 10, y = 100; o tamanho: width = 200, height = 150; e contents: "I 😍 my window", deve devolver a string: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n" - Se contents for nil, a última linha deve ser "[This window intentionally left blank]"

7. Cria um inicializador para a classe Window

O sistema de janelas deve ter um inicializador para que a janela possa ser criada com valores de entrada personalizados. Cria outro inicializador para a classe Window. O inicializador deve receber os seguintes parâmetros: title, contents e dois parâmetros opcionais, size e position. Os parâmetros size e position devem ter como valores predefinidos os valores predefinidos das estruturas Size e Position.

let window = Window(title: "My First Window", contents: "I 😍 my window")
window.display()
// Returns "My First Window\nPosition: (0, 0), Size: (80 x 60)\nI 😍 my window\n"

let window2 = Window(title: "My Second Window", contents: "I 😍 my window", size: Size(width: 200, height: 150), position: Position(x: 10, y: 100))
window2.display()
// Returns "My Second Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n"
Editar via GitHub A ligação abre numa nova janela ou separador
Swift Exercism

Estás pronto para começar Sistema de janelas?

Inscreve-te no Exercism para aprenderes e dominares Swift com 35 conceitos116 exercícios, e mentoria humana real, tudo grátis.