【SwiftUI】見えないボタンを作ってみる

アプリ開発中に「TextField 自体は無効化してキーボードを出したくないけど、タップは受け付けて別の入力画面を開きたい」という要求が出てきたので TextField の上に見えないボタンを乗っけてみるということをやってみましたので紹介します。

【SwiftUI】見えないボタンを作ってみる【InvisibleButton】

見えない、と言っても単に非表示(hidden())にした訳ではありません。なぜかと言うと非表示にしてしまうとタップを検知してもらえなくなるからです。

また、透明度(opacity)を0にしても同じ結果になってしまいます。

そのため、今回は ColorRectangle を組み合わせたボタンを作ってみました。

参考にしたのはこちらの記事です。

InvisibleButton

struct InvisibleButton: View {
    
    let isDebug: Bool
    var onTapped: (() -> ())
    
    init(isDebug: Bool = false, onTapped: @escaping (() -> ())) {
        self.isDebug = isDebug
        self.onTapped = onTapped
    }
    
    var body: some View {
        if isDebug {
            Color.primary.contentShape(Rectangle())
                .opacity(0.2)
                .onTapGesture {
                    onTapped()
                }
        } else {
            Color.clear.contentShape(Rectangle())
                .onTapGesture {
                    onTapped()
                }
        }
    }
}

ポイントは以下の部分です。

Color.clear.contentShape(Rectangle())

透明色(.clear)の Color(Viewの一種)の .contentShape プロパティに Rectangle(矩形)を指定することで実現しています。

イニシャライザー

  • isDebug: Bool デバッグ用の表示フラグです。trueにすると半透明で領域が表示されます。
  • onTapped: (() -> ()) タップ時のアクションを指定します。

使用例

冒頭でも言いましたが、アプリ開発中に、「直接入力できない TextField をタップして別画面に用意したTextEditor で入力した結果を TextField に戻す」と言うことをやりたかったので、同じような感じに再現したコードを紹介します。

struct ContentView: View {
    
    @State var text: String = ""
    @State var isPresented = false
    
    var body: some View {
        HStack {
            Spacer(minLength: 32)
            VStack {
                Spacer(minLength: 8)
                ZStack {
                    TextField("タップして入力", text: $text)
                        .disabled(true)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    InvisibleButton(onTapped: {
                        isPresented = true
                    }).frame(height: 24)
                }
                Spacer(minLength: 8)
            }
            Spacer(minLength: 32)
        }
        .sheet(isPresented: $isPresented) {
            ModalTextView(text: $text)
        }
    }
}

struct ModalTextView: View {
    
    @Binding var text: String
    
    var body: some View {
        TextEditor(text: $text).padding()
    }
}

以下に実行時の動画を掲載しました。

TextFielddisabled(true) となっていますが、上に重なった InvisibleButtononTapped() が動作していることがわかると思います。

実行画面サンプル

以上、ご参考になれば幸いです。