1
1

XcodeのIOSアプリ雛形を最低限いじっておきたい

Posted at

はじめに

ここではSwiftでのIOSアプリ作成の基本をまとめる。雛形はXcodeで提供されており、Hello, Worldを表示するものが提供されている(以下写真は初期状態のままビルド、テストしたもの)。本記事では、都度初歩的な疑問を解消しながら雛形を解説し、最低限いじれるように操作をまとめ、その結果を共有しておく。

スクリーンショット 2024-07-26 18.40.55.png

テンプレのファイル群の解説

Create New Project...よりChoose a template for your new project:をクリックし、IOSタブからAppを選ぶ。そして必要な情報(プロジェクト名や言語等)を入れる。

ここで得られる雛形は"Hello, world!"画面を表示するだけのアプリとなっている。リポジトリの構成は以下の通り。プロジェクト名はhogeとする。それぞれのファイルは何をやっているのだろうか?というか実行ボタンを押したらどのファイルがどういう順序で呼び出されるのだろうか?

hoge
├── hoge
    ├── hogeApp.swift
    ├── ContentView.swift
    ├── Assets
    ├── Preview Content
        ├── Preview Assets
├── hogeTests
    ├── hogeTests.swift
├── hogeUITests
    ├── hogeUITests.swift
    ├── hogeUITestsLaunchTests.swift

順番1. hogeApp.swift

最初に呼び出されるのは、hoge/hoge/ディレクトリ以下のhogeApp.swiftである。このファイルの雛形は以下のようになっている。

hogeApp.swift
import SwiftUI

@main
struct hogeApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

このコードで最も大事なのは@mainアノテーションが付いていることと、構造体がAppプロトコルに準拠していることである。別に構造体の名前はなんでもいいし、構造体でなくてもいい(クラスとかでもワークする)。

hogeApp.swift
import SwiftUI

@main
class Unchi: App {//名前をウンチ(Unchi)に、型をクラスに変更
    required init() {}     
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

@mainアノテーションが付与された型は、アプリケーションのエントリポイントとして機能する。

Q. Appプロトコルって何? てかプロトコルって何?

Swiftにおいてプロトコルとは、型が満たすべき制約を定めて型のインタフェースを決めるものである。
これにより共通の性質を抽象化できる。以下に例を示す

protocol_example1.swift
//プロトコル
protocol Animal {
    func makeSound()
}
//プロトコルに準拠した型A
class Dog: Animal {
    func makeSound() {
        print("Woof!")
    }
}
//プロトコルに準拠した型B
struct Cat: Animal {
    func makeSound() {
        print("Meow!")
    }
}
//型は違えど共通の処理を一括で表現可能
let animals: [Animal] = [Dog(), Cat()]
for animal in animals {
    animal.makeSound()
}
protocol_example2.swift
//プロトコル
protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}
//Intという型
extension Int: Equatable {
    public static func == (lhs: Int, rhs: Int) -> Bool {
        return lhs == rhs
    }
}
//Stringという型
extension String: Equatable {
    public static func == (lhs: String, rhs: String) -> Bool {
        return lhs == rhs
    }
}
//型は違えど共通の処理を表現可能
print(3 == 4)//Int型
print("abc" == "bcd")//String型

これらの例のように、プロトコルは、型に対して制約を求め、それに準拠した型が仮に異なる型であったとしても、共通の処理を行えるようにするためのものである。

Q. Appプロトコルって何?

さてここで、Appプロトコルは型に何を求めるのだろう? 結論、Appプロトコルはbodyプロパティを有しており、bodyプロパティはSwiftUIが用意しているSceneプロトコルに準拠した何かしらの型を返すことを求めている。

hogeApp.swift
import SwiftUI

@main
struct hogeApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

再掲だが、雛形のファイルではbodyプロパティの型の部分にsome Sceneとある。これはSceneプロトコルに準拠した何かしらの型(some)という意味である。
その後に{}が続いていて一瞬戸惑うが、これはコンピューテッドプロパティというやつである。

Q. コンピューテッドプロパティって何?

Swiftのプロパティには値を保持するストアドプロパティと、値を保持しないコンピューテッドプロパティがある。コンピューテッドプロパティは、GetterとSetterを有している。Getterは他のストアドプロパティなどから値を取得して、値を返す。Setterでは暗黙的に使用可能なnewValueの値がGetterの返り値となるように値の更新が行われる。

computed_property.swift
struct length {
    var meter: Double = 0.0
    
    // "centimeter"はコンピューテッドプロパティ
    var centimeter: Double {
        get {
            return 100 * meter
        }
        set {
            meter = 0.01 * newValue
        }
    }
}
print(computed_var.meter) // 0.0
print(computed_var.centimeter) // 0.0
computed_var.meter = 2.5
print(computed_var.meter) // 2.5
print(computed_var.centimeter) // 250 (!meterの値が更新され、centimeterも更新)
computed_var.centimeter = 150
print(computed_var.meter) // 1.5
print(computed_var.centimeter) //150 (!centimeterの値が更新された場合、Setterによりmeterも自動で更新)

また、現実的によく見る例としてSetterがない場合も多く、この場合にはGetter関数自体も端折られる。

computed_property2.swift
struct Rectangle {
    var width: Double
    var height: Double
    
    var area: Double {
        return width * height
    }
}
var rect = Rectangle(width: 5, height: 10)
print(rect.area) // 50.0
rect.width = 7
print(rect.area) // 70.0 (!勝手にwidthと紐づいたareaプロパティも更新されている)

ContentView.swiftにおいては、bodyプロパティがコンピューテッドプロパティである。このプロパティはストアドプロパティでないのは当然で、例えば”ボタンを押す”や”スクロールをする”などのSwiftUIで管理される状態が変化するたびに、ビューを都度反映させる必要がある。そのためbodyプロパティはコンピューテッドプロパティでなければならない。

Q. WindowGroupって何?

 複数Windowが存在する場合、その切り替えを制御したりできるSwiftUIが用意してくれている構造体。一つのWindowしかないアプリを開発する場合がIOSアプリ開発では多い気がするが、これを省略するとAppプロトコルに準拠できなくなる。(ContentViewはSwiftUIが提供するViewプロトコルに準拠しているが、Sceneプロトコルには準拠していない。)

 長くなるので具体的なコードは端折るが、Window制御は例えば以下のように「New Window」ボタンを押したら新Windowが開く形で実装される。

new_window.swift
import SwiftUI

@main
struct hogeApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandMenu("Window") {
                Button("New Window") {
                    // 新しいウィンドウを開くアクション
                }
            }
        }
    }
}

Q. SceneプロトコルとViewプロトコルって何?

 Sceneプロトコルはアプリケーションのライフサイクル管理と主要なUI構成要素の定義に使用され、ウィンドウやウィンドウグループを管理するコンテナとして機能する。
 Viewプロトコルは個々のUIコンポーネントや画面を定義するために使用される(例えばText、Button、ContentViewなど)。これによりユーザーインターフェースの構築に使用する(詳しくはContentViewにて)。

順番2. ContentView.swift

 構造体の名前は別にContentViewでなくても良い(hogeApp.swiftでの呼び出し名を変えれば)。この構造体はViewプロトコルに準拠してさえいれば良い。Viewプロトコルは、bodyプロパティを一つ有していなければならない。これが唯一のビュー定義となる(body2とかはあってはならない)。

ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

bodyプロパティでは、雛形ではVStackというSwiftUIが提供してくれている構造体が定義されている。VStack {..}.padding()とあるが、これはVStack全体に余白を発生させており、VStackで表示させるオブジェクトの好きな場所に余白を生成する(要は別に無くても問題ない)。例えば

ContentView_padding.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()//VStackの周りに均等にデフォルトサイズ分の余白
        .background(Color.red)
        .padding(.vertical, 20)//上下だけ20ポイントの余白
        .background(Color.yellow)
        .padding(.horizontal, 20)//左右だけ20ポイントの余白
        .background(Color.green)

    }
}

のようにpaddingをいじると以下のような誰得Hello Worldも生成できる(paddingの使い方はこういうことではないと思うが)。
スクリーンショット 2024-07-26 17.30.35.png

ちなみにVStack以外にもHstackは横方向にオブジェクトを並べたり、Zstackはオブジェクトを重ねて表示させたり、ListFormなど様々な様式がある。
スクリーンショット 2024-07-26 17.40.19.png

Q. bodyプロパティの中で使えるものは?

  • Text
  • Image
  • Button
  • Spacer
  • Color
    等様々。例えば、Imageメソッドでは自分で撮った画像を使うこともできるし、Buttonボタンを押したらテキストを表示するようなコードも以下のように作成することができる(以下)。
ContentView_button.swift
struct ContentView: View {
    @State private var showStartText = false
    var body: some View {
        if showStartText {
            Text("Done!")
        }
        Button("Push the Button"){
            showStartText = true
        }
    }
}

他にも以下のように背景色が目に優しい誰得Hello Worldも生成できる。

ContentView_color.swift
struct ContentView: View {
    var body: some View {
        ZStack {
            Color.green  // 背景色を緑色に設定
                            .edgesIgnoringSafeArea(.all)  // 全画面に色を適用
            VStack {
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("Hello, world!")
            }
        }
    }
}

スクリーンショット 2024-07-26 18.06.09.png

他にもたくさんファイルがあったけど?

ここまでhogeApp.swiftContentView.swiftを扱ったが、主に操作するファイルはこの二つ(あるいはこいつらが呼び出す雛形とは別の新規作成ファイル)となる。他のファイルも一応解説しておく。

  • Assets:ここに画像をアップロードして、自前の画像を使ったりできる。他にもカスタムColorを定義できたり、JSONやXMLなどのデータファイルを管理できる。例えば自分で用意した画像データをXcodeのAssets上にドラッグ&ドロップでアップロードして、以下のようなZStackを用いることで、ガッキーHello World UIが生成できる。
ContentView_Assets.swift
struct ContentView: View {
    var body: some View {
        ZStack {
            Image("gakkii")
            VStack {
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("Hello, world!")
            }
        }
    }
}

スクリーンショット 2024-07-26 18.32.49.png

  • Preview Content:プレビュー用のコンテンツを管理するためのディレクトリ。
  • hogeTests:ユニットテスト(コンポーネントの動作を確認するために行う)を定義。アプリの動作を確認するためのテストコードが含まれる。
  • hogeUITests:UIテスト(UIが正しく表示されるかを確認するために行う)を定義。アプリのUIを確認するためのテストコードが含まれる。
  • (雛形にはGUI上では表示されていないが)info.plistこれはクソ大事。権限まわりを設定する。例えばカメラアクセスを必要とするアプリではその旨を追記する。
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1