【SwiftUI】ビューからオブジェクトの値を検知する方法

本記事では、SwiftUIで複数プロパティを持つオブジェクトを作成し、UI上からプロパティの値を変更した際に、Viewでプロパティの値を検知して再描画を行う方法を解説していきます。

記事全体としては、オブジェクトの作成→ContentViewでオブジェクトの監視→計算の関数作成→動作の確認という流れで進んでいきます。

監視可能なオブジェクトの作成

Viewからプロパティの内容を監視することができる商品名・値段・数量というプロパティを保有するオブジェクトのクラスを定義していきます。

class Product: ObservableObject {
    @Published var name = ""
    @Published var price = ""
    @Published var count = ""
}

Productクラスは、ObservableObjectプロトコルを指定することによって、ProductクラスのインスタンスがViewでの値の変化に応じて即座にプロパティの値を変更することができます。

プロパティに指定している@Publishedは、このプロパティの値が変更された場合に即座に値の変更をビューに反映することができるという意味を持っています。

このクラスを作成することにより、ObservableObjectプロトコルを指定したクラス内にある@Publishedのついたプロパティの値の変化をViewで検知することが可能となります。

VIewからオブジェクトのプロパティを参照

View側ではProductクラスのインスタンスを作成し、各プロパティをフロントから変更可能となるように設定し、その値が検知されることを確認していきます。

インスタンス化

Productクラスをインスタンス化します。ObservableObjectプロトコルを指定されているクラスをインスタンス化する際には、インスタンスを代入する変数に@ObservedObjectをつけます。

これにより、インスタンス配下のプロパティの値の変化を検知することができるようになります。

@ObservedObject var product = Product()

プロパティの使用

テキストフィールドを3つ用意し、それぞれのテキストにインスタンスのプロパティを設定します。

Group{
            TextField("商品名", text: $product.name)
            TextField("値段", text: $product.price)
            TextField("数量", text: $product.count)
        }.textFieldStyle(RoundedBorderTextFieldStyle())

TextFieldへの入力によって、実際のプロパティの値を変更したいため、変数の前に「$」をつけてプロパティの参照を渡します

参照を渡すことによって、テキストフォームの値を書き換えた際に見た目上の値の変化だけでなくプロパティの値を変更することが可能となります。

ここまででオブジェクトの作成とそのオブジェクトのプロパティをViewから検知する動きは実現することができました。

プロパティの値を計算して描画

Viewで検知したオブジェクトの値を実際に使用して、その値を即時に計算してTextとして表示する機能を作っていきます。

計算する関数の作成

商品の値段と数量から合計金額を算出して返却する「totalPrice」という関数を作成します。

func totalPrice (price:String, count:String) -> String {
    //Stringで受け取った値をIntに変換して計算
    guard let int_price = Int(price) else{
        return ""
    }
    guard let int_count = Int(count) else{
        return ""
    }
    var total = int_count * int_price
    return String(total)
}

オブジェクトの各プロパティはテキスト入力されることを想定しているため、String型で定義しています。なので、totalPrice関数では価格と数量の値をString型のパラメータとして受け取り、Intに変換してから計算し、最後にStringに変換して返却します。

StringからIntに変換する際には、guard let else{}構文を使用しています。この構文は、アプリケーション実行中の予期せぬバグを防ぐために用いています。

Int()は、Stringの文字列をInt型の数値に変換できた場合にはその値を返却し、”abc”や”あいう”などの変換できない文字列が渡された場合にはnilを返却します。guard let else{}構文を使用することで、Int()で変換できずにnilとなるデータだった場合に、elseの中の処理をしてエラーを防ぐことができます。

今回は、””という空の文字列を返却してInt()に返却できなかったことを示しています。

計算結果の表示

totalPrice関数を使用して、View側で入力された値によって表示を切り替える機能を作成します。

let total = totalPrice(price:product.price, count:product.count)
        if( total != ""){
            Text("\(product.name)が\(product.count)つで\(total)円です。")
        }else{
            Text("商品名・値段・個数を入力してください。")
            Text("値段と個数には整数を入力してください。")
        }

定数totalにtotalPrice関数の戻り値を代入します。totalPrice内の処理では、countかpriceの値が数値に変換できない場合には、空文字””が返却されるため、if文で定数totalの値がから文字でないかを判定して処理を分岐させます。

未入力か不正な値が入力された場合にはelse内の処理が実行され、全て想定通りの値が入力された場合には正常系のテキストが表示されるようになりました。

未入力

正常系

不正な値

ContentViewのコード全体

import SwiftUI

struct ContentView: View {
    @ObservedObject var product = Product()
    var body: some View {
        Group{
            TextField("商品名", text: $product.name)
            TextField("値段", text: $product.price)
            TextField("数量", text: $product.count)
        }.textFieldStyle(RoundedBorderTextFieldStyle())
        let total = totalPrice(price:product.price, count:product.count)
        if( total != ""){
            Text("\(product.name)が\(product.count)つで\(total)円です。")
        }else{
            Text("商品名・値段・個数を入力してください。")
            Text("値段と個数には整数を入力してください。")
        }
        
    }
}

#Preview {
    ContentView()
}


class Product: ObservableObject {
    @Published var name = ""
    @Published var price = ""
    @Published var count = ""
}

func totalPrice (price:String, count:String) -> String {
    //Stringで受け取った値をIntに変換して計算
    guard let int_price = Int(price) else{
        return ""
    }
    guard let int_count = Int(count) else{
        return ""
    }
    var total = int_count * int_price
    return String(total)
}

最後に

本記事では、Viewからオブジェクトのプロパティの値の変化を監視して利用する方法について解説してきました。

  • オブジェクトはObservableObjectプロトコルを指定し、プロパティには@Publishedをつける
  • Viewからオブジェクトクラスをインスタンス化する際には、変数に@ObservedObject
  • TextFieldなどの値としてプロパティを使用する際には「$」で参照を渡す

他にも@Binding変数を使用した複数View間での値の連携や、Instagramや旧Twitterなどのように画面下のタブを選択することで画面を切り替えることができるタブビューについても以下の記事で紹介していますのでぜひご覧ください!