おっさんの備忘録

【Swift UI】画面遷移(モーダル)

モーダル遷移

下から上に画面が表示される。

Structure等

sheet

バイディングされた値がtrueのときにシートを表示する。

コード

ボタンをクリックするとフルモーダルを表示する。

struct ContentView: View {
  @State var activeModal = false
  var body: some View {
    VStack {
      Button("モーダル表示") {
        activeModal = true
      }
      .sheet(isPresented: $activeModal) {
        ModalView(example; "Test")
      }
    }
  }
}

struct ModalView: View {
  let example: String
  var body: some View {
    ZStack {
      Text(example)
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

【Swift UI】@State

@State

状態を管理するためのプロパティラッパー。
特定のプロパティが変更されると、関連するビューが自動的に再描画されるようにするための仕組みを提供。

プロパティラッパー

特定のプロパティに対するアクセスや保持方法をカスタマイズするための仕組み。

【Swift UI】画面遷移(ナビゲーション)

ナビゲーション遷移

 左から右に流れるようなアニメーションで遷移する。
 戻るボタンは自動で作成される。

Structure等

ルートビューと追加のビューを表示するためのビュー

 ナビゲーションプレゼンテーションを制御するビュー。
 ユーザはリンクをクリックまたはタップすることによって内側のビューを表示することができる。

コード

2SecondViewをクリックするとSecondViewへ遷移する。

struct ContentView: View {
  var body: some View {
    NavigationStack {
      NavigationLink {
        SecondView()
      } label : {
        Text("2SecondView")
      }
    }
  }
}

struct SecondView : View {
  var body : some View {
    ZStack {
      Color.black
      Text("SecondView").foregroundColor(.white)
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

複数遷移

struct ContentView: View {
  var body: some View {
    NavigationStack {
      List {
        NavigationLink("Apple") { ChildView(en: "Apple", ja: "りんご") }
        NavigationLink("Table") { ChildView(en: "Table", ja: "机") }
      }
      .navigationTitle("英語")
    }
  }
}

struct ChildView: View {
  let en: String
  let ja: String
  var body: some View {
    VStack {
      Text(ja).font(.title)
    }
    .navigationTitle(en)
  }
}

【Swift UI】ファイルの書き込み

概要

テキストファイルに文字列を書き込む

XCodeでプロジェクト作成

XCodeを起動して、[Create a new Xcode project]を選択。
[App]を選択して[Next]をクリック。
[Project Name]に"FileWriter"を入力して[Next]をクリック。
適当なフォルダ(以下A)を選択して[Create]をクリック。
A/FileWriter/FileWriterにXCodeが自動で作成したフォルダ・ファイルが格納されている。

書き込み用のファイルを用意

XCodeの画面のナビゲーター・エリアの[FileWriter]を右クリック(control+クリック)で[New File]を選択。
適当なものを選んで"MyText.txt"を作成する。
FinderでA/FileWriter/FileWriterを見ると"MyText.txt"が作成されている。

ファイル書き込み処理

ContentView.swiftに以下の処理を追加

import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack {
      Text(writeToFile())
    }
  }
}

func writeToFile() -> String {
  guard let fileURL = Bundle.main.url(forResource: "MyText", withExtension: "txt") else {
    return "ファイルが見つかりません"
  }
  
  let text = "書き込みFromApp"
  do {
    let fileHandle = try FileHandle(forWritingTo: fileURL)
    
    fileHandle.seetToEndOfFile()
    fileHandle.write(text.data(using: String.Encoding.utf8))
    // fileHandle.close() // 不要
  } catch {
    return error.localizedDescription
  }
  return "書き込み成功"
}

struct ContentView_Previews: PreviewProvider {
  static var preview: some View {
    ContentView()
  }
}

ファイルの確認

上記のコードが正常に終了していることを確認するために、[Content View]を開く。
"書き込み成功"が表示されている。
しかし、ナビゲーター・エリアの"MyText.txt"をクリックして内容を表示すると何も書き込まれていない。
writeToFile()のfileURL取得後にreturn fileURL.absoluteStringを追加する。
これによって、[Content View]には書き込みを行った"MyText.txt"のパスが表示される。

書き込まれたファイル

ファイルパスは"Users/XX/Library/.../MyText.txt"となっている。
つまり"A/FileWriter/FileWriter/MyText.txt"とは別のファイルに対して操作が行われている。
これはXCodeが持っているシミュレータのパスで、シミュレータに"A/FileWriter/FileWriter/MyText.txt"が転送されているためである。

書き込まれた内容の確認

【Swift UI】ファイルの読み込み - おっさんの備忘録

上記を参照して、書き込み内容を確認すると、書き込みFromAppが4回書き込まれている。

【Swift UI】ファイルの読み込み

概要

テキストファイルを読み込む。読み込んだ文字列を画面に表示する。

コード

import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack {
      Text(getFileStr())
    }
  }
}

func getFileStr() -> String {
  guard let fileURL = Bundle.main.url(forResource: "ファイル名", withExtension: "拡張子"),
    let fileContents = try? String(contentsOf: fileURL, encoding: .utf8) else { // 複数指定のguard let
      return "読み込み失敗"
    }
    return fileContents
}

guard

条件を満たさない場合の処理を記述する構文。
エラーの確認や値がnil(オプショナル型変数にnilが含まれている場合にelseに飛ぶ)でないかのチェックをする。

gurad 条件式 else {
  処理
  returnまたはbreakまたはthrow
}

複数指定したい場合は条件をカンマ区切りで指定。

guard let

代入する値がnilの場合はfalseの処理が行われる。

複数指定したい場合は条件をカンマ区切りで指定

guard let thisStr = str, thisStr.characters.cout >= 5 else {
    
}

Bundleクラス

アプリのバンドルディレクトリにアクセスするためのクラス。
バンドルディレクトリには画像やサウンドファイル、ストーリーボードなどのリソース、テキストファイル、設定ファイルなどが含まれている。
Bundleクラスを使用するにはタイププロパティとして定義されているmainからアクセスする。

Bundle | Apple Developer Documentation

urlメソッド

func url {
  forResource name: String?,
  withExtension ext: String?
} -> URL?
  • name

リソースファイル名。nilを指定した場合はextに指定した拡張子に最初に一致したファイルを返す。

  • ext

リソースファイルの拡張子。空文字またはnilの場合は、拡張子なしのnameに一致したファイルを返す。

  • 戻り値

ファイルのURL。ファイルが見つからなかった場合はnilを返す。

url(forResource:withExtension:) | Apple Developer Documentation

initメソッド

与えられたURLのデータを読み込んでStringを作成する。

init(
  contentsOf url: URL,
  encoding eng: String.Encoding
) throws

init(contentsOf:encoding:) | Apple Developer Documentation


【Swift UI】プロトコル

開発環境

  • macOS Monterey ver12.6.8
  • XCode Version14.2(14C18)

 クラスや構造体が保持するプロパティやメソッドなどの決まりを定めている。
 他の言語のインターフェースに似ている。

コード例

 XCodeで用意されている、1つの画面を表示させるテンプレートであるApp(Single View Application)の初期状態が以下のコード。

ContentView.swift

import SwiftUI
struct ContentView: View {
  var body: some View {
    VStack {
      Text("Hello, world!")
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Viewプロトコル

 私は、上記のコードについて調べてプロトコルとは?となったのがViewプロトコル(Viewの公式ドキュメント)
Overviewを見ると、"Implement the required body computed property to provide the content for your custom view."と書かれている。
 最初に書いたプロパティに関する決まりで、このViewプロトコルに準拠した場合は"計算プロパティのbodyを実装する必要がある"ということ(計算プロパティとbody)
上記のコードでは、VStackという子を縦方向に配置するView(Hello, world!)を返している。

PreviewProvider

【SwifUI】計算プロパティ

計算プロパティ(Computed property)

値を計算するためのプロパティで、以下の特徴を持つ。

  • 固定値は持たない
  • 他のプロパティにアクセスできる
  • 呼び出されたタイミングで処理を行う
  • getとsetが存在

構文

var プロパティ名: 型名 { }

例:税込み価格を計算

struct Price {
  var yen : Double
  let tax_rate = 1.1
  
  var taxIncludedPrice : Double {
    get {
      return yen * tax_rate // 他のプロパティにアクセス
    }
    set {
      yen = newValue / tax_rate
    }
  }
}

var price = Price(yen: 100) 
price.taxIncluded // getが呼び出される(呼び出されたタイミングで処理)
price.taxIncluded = 220 // setが呼び出される

bodyプロパティ

以下のbodyは計算プロパティである。
VStackのViewを返している。

var body: some View {
  VStack {
    Text("return from Body.")
  }
}