トラック
/
Swift
Swift
/
演習
/
ウィンドウシステム
ウィンドウシステム

ウィンドウシステム

学習演習

はじめに

構造体とクラス

構造体とクラスは、Swiftプログラミングの主要な構成要素の2つです。どちらも、関連するデータと関数をまとめて、自己完結した機能の単位にするための仕組みです。そして、構造体やクラスを定義することは、Swiftの中で使う新しい型を定義することです。これまでに扱ってきたIntやStringのような型を使うのと同じことです。

Swiftの構造体とクラスには、多くの共通点があります。いくつもの共通点のなかでも、どちらも値を_プロパティ_に保存でき、_メソッド_を通じて機能を提供できます。

それぞれに追加の機能もありますが、それはこの演習の範囲外です。

構造体とクラスの定義

構造体もクラスも、ほぼ同じ方法で定義します。まず適切なキーワードを書き、その後に定義する型の名前を続けます。そして構造体やクラスの本体を、波括弧の間に書きます。

本体には格納プロパティを書くことができます。格納プロパティは、通常の定数や変数と同じように定義し、同じように振る舞います。

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

インスタンス

前述のとおり、構造体やクラスを定義することは、新しい_型_を定義することにすぎません。それはその型の値がどんな形になるかを示す設計図にすぎず、実際にその型の値を生成して使えるようにするものではありません。

その型の_インスタンス_を生成するには、型の名前を書き、その後に括弧(())を続けます。

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

こうすると、これらの型の値が生成され、プロパティには定義で指定したデフォルト値が設定されます。GameCharacterのnameプロパティのようなオプショナルの場合は、値を指定しない限り、プロパティはデフォルトでnilになります。これは、値をすぐに指定しない通常のオプショナル型を定義するときと同じです。

構造体では、Swiftが自動的に_メンバーワイズイニシャライザー_を提供します。これを使うと、括弧の中に構造体のプロパティの値を指定でき、それが定義内のデフォルト値を上書きします。

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

プロパティへのアクセス

構造体やクラスのプロパティには、_ドット記法_を使ってアクセスできます。値の名前の後にドット(.)とプロパティの名前を書きます。ある構造体やクラスのプロパティがさらに自分のプロパティを持っている場合は、このドット記法でそうした入れ子のプロパティにもアクセスできます。

この記法は、プロパティの値を取得するのにも、許可されている場合は変更するのにも使えます。

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

メソッド

構造体やクラスにデータを保存するプロパティと同様に、構造体やクラスに関数を保存する_メソッド_も定義できます。

メソッドは通常の関数と同じ方法で定義しますが、構造体やクラスの本体の内部に書く点だけが違います。構造体のプロパティの値を変更する関数には、先頭にmutatingキーワードを付ける必要があります。さらに、メソッドから変更できるプロパティは、通常の変数と同じように、letではなくvarを使って定義しなければなりません。

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

構造体やクラスのすべてのインスタンスには、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. Size構造体を定義しましょう

Sizeという名前の構造体を定義し、ウィンドウの現在の幅と高さを格納する2つの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. Position構造体を定義しましょう

Positionという名前の構造体を定義し、ウィンドウの左上隅の現在の水平位置と垂直位置を格納する2つのIntプロパティ、xとyを持たせましょう。 xとyの初期値は、それぞれ0にします。 位置(0, 0)は画面の左上隅で、右へ移動するほどxの値が大きくなり、下へ移動するほどyの値が大きくなります。

新しいxとyをパラメータとして受け取り、プロパティを新しい位置に更新するメソッドmoveTo(newX:newY:)も用意しましょう。

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、初期値はSize構造体のデフォルト値
  • position : Position、初期値はPosition構造体のデフォルト値
  • contents : String?、初期値はnil

このクラスのために、空のイニシャライザも定義しましょう。

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

4. ウィンドウのサイズを変更するメソッドを追加しましょう

  • resize(to:) : (Size) -> () - このメソッドはSize構造体を入力として受け取り、ウィンドウを指定されたサイズに変更しようとします。ただし、新しいサイズは一定の範囲を超えることはできません。
  • 高さと幅の最小値は1です。 1未満の高さや幅が要求された場合は、1に切り詰められます。
  • 高さと幅の最大値は、ウィンドウの現在の位置によって決まります。ウィンドウの端が画面の端を越えることはできません。 この範囲を超える値は、取り得る最大のサイズに切り詰められます。 たとえば、ウィンドウの位置がx = 400、y = 300で、height = 400、width = 300へのサイズ変更が要求された場合、y方向に画面が十分な大きさでないため、ウィンドウはheight = 300、width = 300に変更されます。

5. ウィンドウを移動するメソッドを追加しましょう

  • move(to:) : (Position) -> () - これはresize(to:)と似ていますが、サイズではなく、ウィンドウの_位置_を要求された値に調整するメソッドです。resizeと同様に、新しい位置も一定の範囲を超えることはできません。
  • 位置の最小値は、xとyのどちらも0です。
  • どちらの方向でも、位置の最大値はウィンドウの現在のサイズによって決まります。ウィンドウの端が画面の端を越えることはできません。 この範囲を超える値は、取り得る最大のサイズに切り詰められます。たとえば、ウィンドウのサイズがx = 250、y = 100で、x = 600、y = 200への移動が要求された場合、x方向に画面が十分な大きさでないため、ウィンドウはx = 550、y = 200に移動されます。

6. ウィンドウのテキストを更新してウィンドウの情報を表示するメソッドを追加しましょう

  • update(title:) : (String) -> () - このメソッドは、渡された文字列の値をtitleプロパティに設定します。
  • update(text:) : (String?) -> () - このメソッドは、渡されたオプショナルな文字列の値をcontentsプロパティに設定します。
  • display() : () -> String - このメソッドは、ウィンドウの現在の状態を表す文字列を返します。たとえば、ウィンドウのtitleが"My First Window"、位置がx = 10、y = 100、サイズがwidth = 200、height = 150、contentsが"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クラスにもう1つイニシャライザを作成しましょう。 このイニシャライザは、title、contents、そして2つのオプショナルなパラメータsizeとpositionを取ります。 sizeとpositionパラメータのデフォルト値は、それぞれ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に登録すれば、35個のコンセプト116個の演習、そして本物の人間によるメンタリングとともに、Swiftを学んでマスターできます。すべて無料です。