くるCい

雑記

SwiftUI Textの長さに合わせて図形のサイズを変える

CSSでは当たり前のようにしていたのに、SwiftUIでの書き方がわからなかったので忘れないうちにメモしようと思います。

ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello Worldoooooooo")
                .foregroundColor(.white)
                .padding()
                .background(
                    RoundedRectangle(cornerRadius: 15)
                )
        }
        .padding()
    }
}

2024/04/30 追記

当初はGeometryReaderを使っていたのですが、Textのbackgroundに図形入れるとTextのpaddingに合わせたサイズになったため書き直しました。

SwiftUI TextField 電卓みたいな入力補完

電卓みたいなについて

以下のようなキー操作に対し、以下のような補完と入力制限をすることを目指しました。
キーボードタイプは"."と半角数字のみ入力可能な.decimalPadにします。

キー操作 → 表示

"00123" → "123"
".123" → "0.123"
"0.123.123" → "0.123123"

ペースト値 → 表示

"00123" → "123"
".123" → "0.123"
"0.123.123" → ペーストが行われる前の状態
"あいうえお" → ペーストが行われる前の状態
"0.123" → ペーストが行われる前の状態

大丈夫だとは思いますがこれ以外にも対応が必要なケースがあるかもしれません。

ContentView.swift

import SwiftUI

struct ContentView: View {
    @State private var inputValue = ""
    
    var body: some View {
        TextField("数値を入力してください", text: $inputValue)
            .keyboardType(.decimalPad)
            .onChange(of: inputValue) { [inputValue] newValue in
                // 入力された文字列を数値と"."のみにする
                var filteredText = newValue.filter { "0123456789.".contains($0) }
                // "."の個数を取得
                let dotIndices = filteredText.indices.filter { filteredText[$0] == "." }
                if dotIndices.count > 1 {
                    self.inputValue = inputValue // 直前の状態に戻す
                } else {
                    // "0"から始まる文字列
                    while filteredText.hasPrefix("0") && filteredText.count >= 2 {
                        // 2番目の文字を取得
                        let index = filteredText.index(filteredText.startIndex, offsetBy: 1)
                        let character = filteredText[index]
                        // "."が2番目に存在する場合を除き、先頭の文字を消去
                        if (character == ".") {
                            break;
                        } else {
                            filteredText = String(filteredText.dropFirst())
                        }
                    }
                    // "."が先頭にのみある文字列
                    if filteredText.hasPrefix(".") {
                        filteredText = "0" + filteredText // 先頭に0を追加
                    }
                    self.inputValue = filteredText
                }
            } //.onChangeここまで
    } // bodyここまで
    
} // ContentViewここまで

文字列から数字だけをとり出す方法

isNumberでも数字かどうかを調べることができますが、全角数字が含まれてしまい望ましくなかったので"0123456789"が含まれるか調べることにしました。

全角数字が含まれる

var filteredText = newValue.filter { $0.isNumber || $0 == "."}

全角数字が含まれない

var filteredText = newValue.filter { "0123456789.".contains($0) }

onChange(of:perform:)' was deprecated in iOS 17.0: Use onChange with a two or zero parameter action closure instead.

廃止された古い方のonChangeを使用したので、Minimum DeploymentをiOS17.0以上にすると黄色の警告がでます。

作った理由

TextFieldに入力した金額をDBに追加する前のバリデーションとして作りました。
TextFieldに数値型の変数を渡しフォーマットを.numberにする方法は、見た目上でTextFieldにペーストや入力できてしまうので使いませんでした。