Διαδρομές
/
Swift
Swift
/
Ασκήσεις
/
Σύστημα Παραθύρων
Σύστημα Παραθύρων

Σύστημα Παραθύρων

Άσκηση εκμάθησης

Εισαγωγή

Struct και κλάσεις

Τα struct και οι κλάσεις είναι δύο από τα βασικά δομικά στοιχεία του προγραμματισμού σε Swift. Και τα δύο είναι τρόποι ομαδοποίησης σχετικών δεδομένων και συναρτήσεων σε αυτοτελείς μονάδες λειτουργικότητας. Και όταν ορίζεις ένα struct ή μια κλάση, ορίζεις έναν νέο τύπο που θα χρησιμοποιηθεί στο Swift, όπως ακριβώς χρησιμοποίησες τους τύπους με τους οποίους έχεις ήδη δουλέψει, όπως Int και String.

Υπάρχουν πολλές ομοιότητες μεταξύ struct και κλάσεων στη Swift. Μεταξύ άλλων ομοιοτήτων, και τα δύο μπορούν να αποθηκεύουν τιμές σε ιδιότητες και να παρέχουν λειτουργικότητα μέσω της χρήσης μεθόδων.

Το καθένα παρέχει κάποιες επιπλέον λειτουργίες, οι οποίες είναι εκτός του πεδίου αυτής της άσκησης.

Ορισμός struct και κλάσεων

Τόσο τα struct όσο και οι κλάσεις ορίζονται με περίπου τον ίδιο τρόπο. Ξεκινούν με την κατάλληλη λέξη-κλειδί ακολουθούμενη από το όνομα του τύπου που ορίζεται, και στη συνέχεια ακολουθεί το σώμα του struct/της κλάσης, τοποθετημένο μέσα σε άγκιστρα.

Το σώμα μπορεί να αποτελείται από αποθηκευμένες ιδιότητες, οι οποίες ορίζονται και συμπεριφέρονται ακριβώς όπως οι συνηθισμένες σταθερές ή μεταβλητές.

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

Στιγμιότυπα

Όπως σημειώθηκε παραπάνω, ο ορισμός ενός struct ή μιας κλάσης είναι απλώς ο ορισμός ενός νέου τύπου. Είναι απλώς το σχέδιο για το πώς θα μοιάζουν οι τιμές αυτού του τύπου, αλλά δεν δημιουργεί στην πραγματικότητα καμία τιμή αυτού του τύπου για να δουλέψεις.

Για να δημιουργήσεις ένα στιγμιότυπο αυτού του τύπου, πρέπει να γράψεις το όνομα του τύπου ακολουθούμενο από ένα ζεύγος παρενθέσεων.

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

Αυτό θα δημιουργήσει τιμές αυτών των τύπων, όπου οι ιδιότητες συμπληρώνονται με τις προεπιλεγμένες τιμές που παρέχονται στον ορισμό. Σημείωσε ότι σε προαιρετικές περιπτώσεις όπως η ιδιότητα name του GameCharacter, εκτός αν παρέχεται μια τιμή, η ιδιότητα θα έχει προεπιλεγμένη τιμή nil, όπως ακριβώς συμβαίνει με τον ορισμό κανονικών προαιρετικών τύπων όπου δεν παρέχεται άμεσα μια τιμή.

Με τα struct, η Swift παρέχει αυτόματα κάτι που ονομάζεται αρχικοποιητής κατά μέλη, όπου οι τιμές για τις ιδιότητες του struct μπορούν να παρέχονται μέσα στις παρενθέσεις, οι οποίες θα υπερισχύσουν των προεπιλεγμένων τιμών στον ορισμό.

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

Πρόσβαση σε ιδιότητες

Οι ιδιότητες struct και κλάσεων μπορούν να προσπελαστούν χρησιμοποιώντας συμβολισμό με τελεία, όπου το όνομα της τιμής ακολουθείται από μια τελεία (.) και το όνομα της ιδιότητας. Εάν μια ιδιότητα ενός struct ή μιας κλάσης έχει τις δικές της ιδιότητες, αυτός ο συμβολισμός με τελεία μπορεί να χρησιμοποιηθεί για την πρόσβαση και σε αυτές τις εμφωλευμένες ιδιότητες.

Αυτός ο συμβολισμός μπορεί να χρησιμοποιηθεί τόσο για την ανάκτηση της τιμής της ιδιότητας όσο και, όπου επιτρέπεται, για την αλλαγή της.

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

Μέθοδοι

Όπως οι ιδιότητες, που αποθηκεύουν δεδομένα στα struct και στις κλάσεις σου, μπορείς επίσης να ορίσεις μεθόδους που αποθηκεύουν συναρτήσεις στο struct ή στην κλάση σου.

Οι μέθοδοι ορίζονται με τον ίδιο τρόπο όπως μια κανονική συνάρτηση, μόνο που βρίσκονται μέσα στο σώμα του struct ή της κλάσης. Σημείωσε ότι εάν μια συνάρτηση αλλάζει την τιμή μιας ιδιότητας σε ένα struct, πρέπει να προηγείται η λέξη-κλειδί mutating. Επιπλέον, εάν μια ιδιότητα μπορεί να αλλάξει από μια μέθοδο, αυτή η ιδιότητα πρέπει να οριστεί χρησιμοποιώντας var αντί για let, όπως ακριβώς οι κανονικές μεταβλητές.

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

Αυτές οι μέθοδοι μπορούν να κληθούν χρησιμοποιώντας συμβολισμό με τελεία, όπως ακριβώς οι ιδιότητες.

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

Κάθε στιγμιότυπο ενός struct ή μιας κλάσης έχει μια σιωπηρή ιδιότητα με το όνομα self, η οποία αναφέρεται στο ίδιο το στιγμιότυπο. Η πιο συνηθισμένη χρήση του self είναι να αποσαφηνίσει τη διάκριση ανάμεσα σε μια ιδιότητα στιγμιότυπου και σε μια παράμετρο που έχει το ίδιο όνομα:

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

Οι τύποι της Swift παρέχουν επίσης μια λέξη-κλειδί Self με κεφαλαίο αρχικό γράμμα, η οποία αναφέρεται στον τύπο του στιγμιότυπου και όχι στο ίδιο το στιγμιότυπο.

Οδηγίες

Σε αυτή την άσκηση, θα προσομοιώσεις ένα σύστημα υπολογιστή βασισμένο σε παράθυρα. Θα δημιουργήσεις μερικά παράθυρα που μπορούν να μετακινηθούν και να αλλάξουν μέγεθος και να εμφανίσουν τα περιεχόμενά τους. Η παρακάτω εικόνα είναι αντιπροσωπευτική των τιμών με τις οποίες θα δουλέψεις πιο κάτω.

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

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

1. Όρισε ένα struct Size

Όρισε ένα struct με όνομα Size με δύο ιδιότητες Int, τις width και height, που αποθηκεύουν το τρέχον πλάτος και ύψος του παραθύρου, αντίστοιχα. Το αρχικό πλάτος και ύψος θα πρέπει να είναι 80 και 60, αντίστοιχα. Συμπερίλαβε μια μέθοδο resize(newWidth:newHeight:) που δέχεται παραμέτρους για το νέο πλάτος και ύψος και αλλάζει τις ιδιότητες ώστε να αντικατοπτρίζουν το νέο μέγεθος.

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. Όρισε ένα struct Position

Όρισε ένα struct με όνομα Position με δύο ιδιότητες Int, τις x και y, που αποθηκεύουν την τρέχουσα οριζόντια και κάθετη θέση, αντίστοιχα, της πάνω αριστερής γωνίας του παραθύρου. Οι αρχικές τιμές των x και y θα πρέπει να είναι 0 και οι δύο. Η θέση (0, 0) είναι η πάνω αριστερή γωνία της οθόνης, με τις τιμές του x να μεγαλώνουν καθώς κινείσαι δεξιά και τις τιμές του y να μεγαλώνουν καθώς κινείσαι προς τα κάτω.

Συμπερίλαβε μια μέθοδο moveTo(newX:newY:) που δέχεται παραμέτρους για το νέο x και y και αλλάζει τις ιδιότητες ώστε να αντικατοπτρίζουν τη νέα θέση.

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

3. Όρισε μια κλάση Window

Όρισε μια κλάση παραθύρου με τις ακόλουθες ιδιότητες:

  • title : String, η αρχική τιμή είναι "New Window"
  • screenSize : Size, σταθερή τιμή με width = 800 και height = 600
  • size : Size, η αρχική τιμή είναι η προεπιλεγμένη τιμή του struct Size
  • position : Position, η αρχική τιμή είναι η προεπιλεγμένη τιμή του struct Position
  • contents : String?, η αρχική τιμή είναι nil

Θα πρέπει επίσης να ορίσεις έναν κενό αρχικοποιητή για την κλάση.

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

4. Πρόσθεσε μια μέθοδο για αλλαγή μεγέθους παραθύρων

  • resize(to:) : (Size) -> () - Αυτή η μέθοδος δέχεται ένα struct Size ως είσοδο και επιχειρεί να αλλάξει το μέγεθος του παραθύρου στο καθορισμένο μέγεθος. Ωστόσο, το νέο μέγεθος δεν μπορεί να υπερβεί ορισμένα όρια.
  • Το ελάχιστο επιτρεπόμενο ύψος ή πλάτος είναι 1. Ζητούμενα ύψη ή πλάτη μικρότερα από 1 θα περικόπτονται στο 1.
  • Το μέγιστο ύψος και πλάτος εξαρτάται από την τρέχουσα θέση του παραθύρου· τα άκρα του παραθύρου δεν μπορούν να ξεπεράσουν τα άκρα της οθόνης. Τιμές μεγαλύτερες από αυτά τα όρια θα περικόπτονται στο μεγαλύτερο μέγεθος που μπορούν να πάρουν. Π.χ. αν η θέση του παραθύρου είναι x = 400, y = 300 και ζητηθεί αλλαγή μεγέθους σε height = 400, width = 300, τότε το παράθυρο θα αλλάξει μέγεθος σε height = 300, width = 300, καθώς η οθόνη δεν είναι αρκετά μεγάλη στην κατεύθυνση y για να χωρέσει πλήρως το αίτημα.

5. Πρόσθεσε μια μέθοδο για μετακίνηση παραθύρων

  • move(to:) : (Position) -> () - Αυτή είναι παρόμοια με τη resize(to:), ωστόσο, αυτή η μέθοδος προσαρμόζει τη θέση του παραθύρου στη ζητούμενη τιμή, αντί για το μέγεθος. Όπως και με τη resize, η νέα θέση δεν μπορεί να υπερβεί ορισμένα όρια.
  • Η μικρότερη θέση είναι 0 και για το x και για το y.
  • Η μέγιστη θέση προς οποιαδήποτε κατεύθυνση εξαρτάται από το τρέχον μέγεθος του παραθύρου· τα άκρα δεν μπορούν να ξεπεράσουν τα άκρα της οθόνης. Τιμές μεγαλύτερες από αυτά τα όρια θα περικόπτονται στο μεγαλύτερο μέγεθος που μπορούν να πάρουν. Π.χ. αν το μέγεθος του παραθύρου είναι x = 250, y = 100 και ζητηθεί μετακίνηση σε x = 600, y = 200, τότε το παράθυρο θα μετακινηθεί σε x = 550, y = 200, καθώς η οθόνη δεν είναι αρκετά μεγάλη στην κατεύθυνση x για να χωρέσει πλήρως το αίτημα.

6. Πρόσθεσε μεθόδους για την ενημέρωση του κειμένου του παραθύρου και την εμφάνιση πληροφοριών του παραθύρου

  • update(title:) : (String) -> () - Αυτή η μέθοδος ορίζει την ιδιότητα title στην τιμή της συμβολοσειράς που πέρασε ως όρισμα.
  • update(text:) : (String?) -> () - Αυτή η μέθοδος ορίζει την ιδιότητα contents στην τιμή της προαιρετικής συμβολοσειράς που πέρασε ως όρισμα.
  • display() : () -> String - Αυτή η μέθοδος επιστρέφει μια συμβολοσειρά που περιγράφει την τρέχουσα κατάσταση του παραθύρου. Για παράδειγμα, αν το παράθυρο έχει title "My First Window" με θέση: x = 10, y = 100· μέγεθος: width = 200, height = 150· και περιεχόμενα: "I 😍 my window", θα πρέπει να επιστρέψει τη συμβολοσειρά: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n" - Αν το contents είναι nil, η τελευταία γραμμή θα πρέπει να είναι "[This window intentionally left blank]"

7. Δημιούργησε έναν αρχικοποιητή για την κλάση Window

Το σύστημα παραθύρων θα πρέπει να έχει έναν αρχικοποιητή, ώστε το παράθυρο να μπορεί να δημιουργηθεί με προσαρμοσμένα δεδομένα εισόδου. Δημιούργησε άλλον έναν αρχικοποιητή για την κλάση Window. Ο αρχικοποιητής θα πρέπει να δέχεται τις εξής παραμέτρους: title, contents, και δύο προαιρετικές παραμέτρους size και position. Οι παράμετροι size και position θα πρέπει να έχουν ως προεπιλεγμένες τιμές τις προεπιλεγμένες τιμές των struct Size και 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"
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
Swift Exercism

Έτοιμος να ξεκινήσεις την άσκηση Σύστημα Παραθύρων;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις Swift με 35 έννοιες116 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.