Tracks
/
Swift
Swift
/
Ejercicios
/
Sistema de ventanas
Sistema de ventanas

Sistema de ventanas

Ejercicio de aprendizaje

Introducción

Estructuras y clases

Las estructuras y las clases son dos de los bloques fundamentales de la programación en Swift. Ambas son formas de agrupar datos y funciones relacionados en unidades de funcionalidad autocontenidas. Y cuando defines una estructura o una clase, estás definiendo un tipo nuevo que se usará en Swift, tal como usaste los tipos con los que ya trabajaste, como Int y String.

Hay muchas similitudes entre las estructuras y las clases en Swift. Entre otras, ambas pueden almacenar valores en propiedades y ofrecer funcionalidad mediante el uso de métodos.

Cada una ofrece funcionalidad adicional, que queda fuera del alcance de este ejercicio.

Definir estructuras y clases

Tanto las estructuras como las clases se definen más o menos de la misma manera. Empiezan con la palabra clave correspondiente, seguida del nombre del tipo que se está definiendo; después viene el cuerpo de la estructura o la clase, entre llaves.

El cuerpo puede estar formado por propiedades almacenadas, que se definen y se comportan igual que una constante o una variable normales.

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()
}

Instancias

Como se mencionó antes, definir una estructura o una clase es solo definir un tipo nuevo. Es solo el plano de cómo se verán los valores de ese tipo, pero en realidad no crea ningún valor de ese tipo para que trabajes con él.

Para crear una instancia de ese tipo, debes escribir el nombre del tipo seguido de un par de paréntesis.

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

Esto creará valores de estos tipos, con las propiedades inicializadas con los valores predeterminados que se indican en la definición. Ten en cuenta que, en casos opcionales como la propiedad name de GameCharacter, si no se proporciona un valor, la propiedad toma el valor nil de forma predeterminada, igual que cuando defines tipos opcionales normales en los que no se proporciona un valor de inmediato.

Con las estructuras, Swift proporciona automáticamente algo llamado inicializador por miembros, en el que puedes indicar valores para las propiedades de la estructura dentro de los paréntesis, lo que sobrescribe los valores predeterminados de la definición.

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

Acceder a las propiedades

Se puede acceder a las propiedades de estructuras y clases usando la notación de punto, en la que el nombre del valor va seguido de un punto (.) y del nombre de la propiedad. Si una propiedad de una estructura o de una clase tiene propiedades propias, también puedes usar esta notación de punto para acceder a esas propiedades anidadas.

Puedes usar esta notación tanto para obtener el valor de la propiedad como, cuando esté permitido, para cambiarlo.

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

Así como las propiedades almacenan datos en tus estructuras y clases, también puedes definir métodos, que almacenan funciones en tu estructura o clase.

Los métodos se definen igual que una función normal, solo que dentro del cuerpo de la estructura o la clase. Ten en cuenta que, si una función cambia el valor de una propiedad de una estructura, debe ir precedida de la palabra clave mutating. Además, si un método puede cambiar una propiedad, esa propiedad debe definirse con var en lugar de let, igual que las variables normales.

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."
    }
  }
}

Puedes llamar a estos métodos usando la notación de punto, igual que con las propiedades.

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 instancia de una estructura o una clase tiene una propiedad implícita llamada self, que hace referencia a la instancia misma. El uso más común de self es distinguir entre una propiedad de la instancia y un parámetro que tiene el mismo nombre:

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
    }
}

Los tipos de Swift también ofrecen una palabra clave Self con mayúscula inicial, que hace referencia al tipo de la instancia en lugar de a la instancia misma.

Instrucciones

En este ejercicio, vas a simular un sistema de computadora basado en ventanas. Vas a crear algunas ventanas que se pueden mover y redimensionar, y que muestran su contenido. La siguiente imagen es representativa de los valores con los que vas a trabajar a continuación.

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

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

1. Define una estructura Size

Define una estructura llamada Size con dos propiedades Int, width y height, que almacenan el ancho y la altura actuales de la ventana, respectivamente. El ancho y la altura iniciales deben ser 80 y 60, respectivamente. Incluye un método resize(newWidth:newHeight:) que recibe nuevos parámetros de ancho y altura y cambia las propiedades para reflejar el nuevo tamaño.

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 una estructura Position

Define una estructura llamada Position con dos propiedades Int, x y y, que almacenan la posición horizontal y vertical actual, respectivamente, de la esquina superior izquierda de la ventana. Los valores iniciales de x e y deben ser 0 en ambos casos. La posición (0, 0) es la esquina superior izquierda de la pantalla, con valores de x que aumentan a medida que te mueves a la derecha y valores de y que aumentan a medida que te mueves hacia abajo.

Incluye un método moveTo(newX:newY:) que recibe nuevos parámetros x e y y cambia las propiedades para reflejar la nueva posición.

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

3. Define una clase Window

Define una clase de ventana con las siguientes propiedades:

  • title : String, el valor inicial es «New Window»
  • screenSize : Size, valor constante con width = 800 y height = 600
  • size : Size, el valor inicial es el valor predeterminado de la estructura Size
  • position : Position, el valor inicial es el valor predeterminado de la estructura Position
  • contents : String?, el valor inicial es nil

También debes definir un inicializador vacío para la clase.

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

4. Agrega un método para redimensionar ventanas

  • resize(to:) : (Size) -> () - Este método recibe una estructura Size como argumento e intenta redimensionar la ventana al tamaño especificado. Sin embargo, el nuevo tamaño no puede exceder ciertos límites.
  • La altura o el ancho mínimo permitido es 1. Las alturas o los anchos solicitados menores que 1 se recortarán a 1.
  • La altura y el ancho máximos dependen de la posición actual de la ventana: los bordes de la ventana no pueden pasar más allá de los bordes de la pantalla. Los valores mayores que estos límites se recortarán al tamaño más grande que puedan tomar. Por ejemplo, si la posición de la ventana está en x = 400, y = 300 y se solicita un redimensionado a height = 400, width = 300, entonces la ventana se redimensionará a height = 300, width = 300, ya que la pantalla no es lo suficientemente grande en la dirección y para satisfacer completamente la solicitud.

5. Agrega un método para mover ventanas

  • move(to:) : (Position) -> () - Esto es similar a resize(to:); sin embargo, este método ajusta la posición de la ventana al valor solicitado, en lugar del tamaño. Al igual que con resize, la nueva posición no puede exceder ciertos límites.
  • La posición más pequeña es 0 tanto para x como para y.
  • La posición máxima en cualquier dirección depende del tamaño actual de la ventana; los bordes no pueden pasar más allá de los bordes de la pantalla. Los valores mayores que estos límites se recortarán al tamaño más grande que puedan tomar. Por ejemplo, si el tamaño de la ventana está en x = 250, y = 100 y se solicita un movimiento a x = 600, y = 200, entonces la ventana se moverá a x = 550, y = 200, ya que la pantalla no es lo suficientemente grande en la dirección x para satisfacer completamente la solicitud.

6. Agrega métodos para actualizar el texto de la ventana y mostrar la información de la ventana

  • update(title:) : (String) -> () - Este método establece la propiedad title al valor del string que se pasó.
  • update(text:) : (String?) -> () - Este método establece la propiedad contents al valor del string opcional que se pasó.
  • display() : () -> String - Este método devuelve un string que describe el estado actual de la ventana. Por ejemplo, si la ventana tiene el title «My First Window» con posición: x = 10, y = 100; tamaño: width = 200, height = 150; y contenido: «I 😍 my window», debe devolver el string: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n" - Si contents es nil, la última línea debe decir «[This window intentionally left blank]»

7. Crea un inicializador para la clase Window

El sistema de ventanas debe tener un inicializador para que la ventana se pueda crear con argumentos personalizados. Crea otro inicializador para la clase Window. El inicializador debe recibir los siguientes parámetros: title, contents y dos parámetros opcionales size y position. Los parámetros size y position deben tener como valores predeterminados los valores predeterminados de las estructuras Size y 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 en GitHub El enlace se abre en una ventana o una pestaña nuevas
Swift Exercism

¿Todo listo para empezar Sistema de ventanas?

Regístrate en Exercism para aprender y dominar Swift con 35 conceptos116 ejercicios y mentoría humana real, todo gratis.