Track
/
Swift
Swift
/
Esercizi
/
Sistema a finestre
Sistema a finestre

Sistema a finestre

Esercizio di apprendimento

Introduzione

Struct e classi

Le struct e le classi sono due dei principali elementi costitutivi della programmazione in Swift. Entrambe sono un modo per raggruppare dati e funzioni correlate in unità di funzionalità autonome. E quando definisci una struct o una classe, stai definendo un nuovo tipo da usare in Swift, proprio come hai usato i tipi con cui hai già lavorato, come Int e String.

Ci sono molte somiglianze tra struct e classi in Swift. Tra le altre cose, entrambe sono in grado di memorizzare valori in proprietà e di offrire funzionalità tramite l'uso di metodi.

Ognuna fornisce anche funzionalità aggiuntive, che esulano da questo esercizio.

Definire struct e classi

Sia le struct sia le classi si definiscono più o meno allo stesso modo. Si inizia con la parola chiave appropriata seguita dal nome del tipo che si sta definendo, poi segue il corpo della struct o della classe, racchiuso tra parentesi graffe.

Il corpo può essere formato da proprietà memorizzate, che si definiscono e si comportano proprio come normali costanti o variabili.

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

Istanze

Come detto sopra, definire una struct o una classe significa semplicemente definire un nuovo tipo. È soltanto il progetto di come saranno i valori di quel tipo, ma non crea davvero alcun valore di quel tipo con cui lavorare.

Per creare un'istanza di quel tipo, devi scrivere il nome del tipo seguito da una coppia di parentesi tonde (()).

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

Questo creerà valori di questi tipi, con le proprietà popolate con i valori predefiniti forniti nella definizione. Nota che nei casi opzionali come la proprietà name di GameCharacter, se non viene fornito un valore, la proprietà sarà nil per impostazione predefinita, proprio come quando si definiscono normali tipi opzionali in cui non viene fornito subito un valore.

Con le struct, Swift fornisce automaticamente qualcosa chiamato inizializzatore memberwise, in cui i valori per le proprietà della struct possono essere forniti tra parentesi tonde, sovrascrivendo i valori predefiniti della definizione.

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

Accedere alle proprietà

Si può accedere alle proprietà di struct e classi usando la notazione a punto, in cui il nome del valore è seguito da un punto (.) e dal nome della proprietà. Se una proprietà di una struct o di una classe ha proprietà proprie, questa notazione a punto può essere usata per accedere anche a queste proprietà annidate.

Questa notazione può essere usata sia per recuperare il valore della proprietà sia, dove consentito, per modificarlo.

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

Metodi

Come le proprietà, che memorizzano dati nelle struct e nelle classi, puoi anche definire metodi, che memorizzano funzioni nella struct o nella classe.

I metodi si definiscono allo stesso modo di una normale funzione, solo all'interno del corpo della struct o della classe. Nota che se una funzione cambia il valore di una proprietà in una struct, deve essere preceduta dalla parola chiave mutating. Inoltre, se una proprietà può essere modificata da un metodo, quella proprietà deve essere definita con var invece che con let, proprio come le normali variabili.

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

Questi metodi possono essere chiamati con la notazione a punto, proprio come le proprietà.

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

Ogni istanza di una struct o di una classe ha una proprietà implicita chiamata self, che si riferisce all'istanza stessa. L'uso più comune di self è disambiguare tra una proprietà dell'istanza e un parametro che ha lo stesso 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
    }
}

Anche i tipi di Swift forniscono una parola chiave Self con l'iniziale maiuscola, che si riferisce al tipo dell'istanza invece che all'istanza stessa.

Istruzioni

In questo esercizio simulerai un sistema informatico basato su finestre. Creerai alcune finestre che si possono spostare e ridimensionare e che mostrano il loro contenuto. L'immagine seguente è rappresentativa dei valori con cui lavorerai qui sotto.

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

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

1. Definisci una struct Size

Definisci una struct di nome Size con due proprietà Int, width e height, che memorizzano rispettivamente la larghezza e l'altezza attuali della finestra. La larghezza e l'altezza iniziali devono essere rispettivamente 80 e 60. Includi un metodo resize(newWidth:newHeight:) che accetta come parametri la nuova larghezza e la nuova altezza e modifica le proprietà per riflettere le nuove dimensioni.

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. Definisci una struct Position

Definisci una struct di nome Position con due proprietà Int, x e y, che memorizzano rispettivamente la posizione orizzontale e verticale attuali dell'angolo in alto a sinistra della finestra. I valori iniziali di x e y devono essere entrambi 0. La posizione (0, 0) è l'angolo in alto a sinistra dello schermo: i valori di x aumentano man mano che ci si sposta verso destra e i valori di y aumentano man mano che ci si sposta verso il basso.

Includi un metodo moveTo(newX:newY:) che accetta come parametri la nuova x e la nuova y e modifica le proprietà per riflettere la nuova posizione.

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

3. Definisci una classe Window

Definisci una classe Window con le seguenti proprietà:

  • title : String, il valore iniziale è "New Window"
  • screenSize : Size, valore costante con width = 800 e height = 600
  • size : Size, il valore iniziale è quello predefinito della struct Size
  • position : Position, il valore iniziale è quello predefinito della struct Position
  • contents : String?, il valore iniziale è nil

Definisci anche un inizializzatore vuoto per la classe.

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

4. Aggiungi un metodo per ridimensionare le finestre

  • resize(to:) : (Size) -> (). Questo metodo accetta come input una struct Size e prova a ridimensionare la finestra alle dimensioni specificate. Tuttavia, le nuove dimensioni non possono superare certi limiti.
  • L'altezza o la larghezza minime consentite sono 1. Altezze o larghezze richieste inferiori a 1 vengono ridotte a 1.
  • L'altezza e la larghezza massime dipendono dalla posizione attuale della finestra: i bordi della finestra non possono oltrepassare i bordi dello schermo. I valori superiori a questi limiti vengono ridotti alla dimensione massima possibile. Ad esempio, se la posizione della finestra è x = 400, y = 300 e viene richiesto un ridimensionamento a height = 400, width = 300, la finestra verrà ridimensionata a height = 300, width = 300, perché lo schermo non è abbastanza grande nella direzione y per soddisfare completamente la richiesta.

5. Aggiungi un metodo per spostare le finestre

  • move(to:) : (Position) -> (). È simile a resize(to:), ma questo metodo regola la posizione della finestra al valore richiesto, non le dimensioni. Come per resize, la nuova posizione non può superare certi limiti.
  • La posizione minima è 0 sia per x che per y.
  • La posizione massima in entrambe le direzioni dipende dalle dimensioni attuali della finestra: i bordi non possono oltrepassare i bordi dello schermo. I valori superiori a questi limiti vengono ridotti alla dimensione massima possibile. Ad esempio, se le dimensioni della finestra sono x = 250, y = 100 e viene richiesto uno spostamento a x = 600, y = 200, la finestra verrà spostata a x = 550, y = 200, perché lo schermo non è abbastanza grande nella direzione x per soddisfare completamente la richiesta.

6. Aggiungi metodi per aggiornare il testo della finestra e mostrare le informazioni sulla finestra

  • update(title:) : (String) -> (). Questo metodo imposta la proprietà title al valore della stringa passata.
  • update(text:) : (String?) -> (). Questo metodo imposta la proprietà contents al valore della stringa opzionale passata.
  • display() : () -> String. Questo metodo restituisce una stringa che descrive lo stato attuale della finestra. Ad esempio, se la finestra ha il title "My First Window" con posizione: x = 10, y = 100; dimensioni: width = 200, height = 150; e contents: "I 😍 my window", dovrebbe restituire la stringa: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n". Se contents è nil, l'ultima riga dovrebbe essere "[This window intentionally left blank]"

7. Crea un inizializzatore per la classe Window

Il sistema a finestre dovrebbe avere un inizializzatore che permetta di creare la finestra con input personalizzati. Crea un altro inizializzatore per la classe Window. L'inizializzatore dovrebbe accettare i seguenti parametri: title, contents e due parametri opzionali, size e position. I parametri size e position dovrebbero avere come valori predefiniti quelli predefiniti delle struct 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"
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Swift Exercism

Vuoi iniziare Sistema a finestre?

Iscriviti a Exercism per imparare e padroneggiare Swift con 35 concetti116 esercizi e il mentoring di persone reali, tutto gratis.