[SwiftUI]ラジオボタンの実装

目次

概要

選択肢が少ないものを選択する場合や、選択した上で何かの入力を行う場合、
ラジオボタンが必要になる。
ネット通販をする際、登録した住所に送るか、別の住所に送ることを選択した上で住所を入力する場合など。

Listを使用しないパターン

レイアウトを自由に設定したいときや、Listを使用するほど画面の領域を使用したくないときに使うと思う。

ソースコード

import SwiftUI

@main
struct TestApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            RadioButtonsView()
        }
    }
}
import SwiftUI

struct RadioButtonsView: View {
    @ObservedObject var viewModel: RadioButtonsViewModel = RadioButtonsViewModel()
    
    let jobs = ["戦士", "武闘家", "僧侶", "魔法使い", "商人", "遊び人", "盗賊", "賢者"]
    
    var body: some View {
        VStack {
            Text("職業を選んでください")
            verticalLayout()
        }
    }
    
    private func verticalLayout() -> some View {
        return VStack (alignment: .leading) {
            ForEach(jobs, id: \.self) { job in
                radioButtonView(job: job)
                    .onTapGesture {
                        self.viewModel.updateSelectedJob(selectedJob: job)
                    }
            }
        }
    }
    
    // ラジオボタンと項目名のView
    private func radioButtonView(job: String) -> some View {
        return HStack {
            Button {
                self.viewModel.updateSelectedJob(selectedJob: job)
            } label: {
                Image(systemName: self.viewModel.getRadioButtonImage(job: job))
                    .font(.system(size: 20))
            }
            Text(job)
                .font(.system(size: 20))
        }
    }
}
import Foundation
import SwiftUI

class RadioButtonsViewModel: NSObject, ObservableObject {
    @Published var selectedJob: String = ""
    
    func updateSelectedJob(selectedJob: String) {
        self.selectedJob = selectedJob
    }
    
    // ラジオボタンのシステム名を返す
    func getRadioButtonImage(job: String) -> String {
        return self.selectedJob == job ? "button.programmable" : "circle"
    }
    
    // ラジオボタンの色を返す
    func getRadioButtonColor(job: String) -> Color {
        return self.selectedJob == job ? Color.blue : Color.gray
    }
}

デモ動画

Listを使用するパターン

レイアウトを綺麗に整理したいとき、設定画面など他のリストも使用する際に使われると思う。

ソースコード

「TestApp.swift」と「RadioButtonsViewModel.swift」は同じソースコード

import SwiftUI

struct RadioButtonsView: View {
    @ObservedObject var viewModel: RadioButtonsViewModel = RadioButtonsViewModel()
    
    let jobs = ["戦士", "武闘家", "僧侶", "魔法使い", "商人", "遊び人", "盗賊", "賢者"]
    
    var body: some View {
        VStack {
            Text("職業を選んでください")
            listLayout()
        }
    }
    
    private func listLayout() -> some View {
        return List {
            ForEach(jobs, id: \.self) { job in
                radioButtonCell(job: job)
                    .contentShape(Rectangle())
                    .onTapGesture {
                        self.viewModel.updateSelectedJob(selectedJob: job)
                    }
            }
        }
    }
    
    // ラジオボタンと項目名のセル(セル全体がタップ対象領域)
    private func radioButtonCell(job: String) -> some View {
        return HStack {
            Image(systemName: self.viewModel.getRadioButtonImage(job: job))
                .font(.system(size: 20))
                .foregroundColor(self.viewModel.getRadioButtonColor(job: job))
            Text(job)
                .font(.system(size: 20))
            Spacer()
        }
    }
}

デモ動画

詳細

radioButtonCellの最後に「Spacer()」を入れることで、セルのラジオボタンに関するViewがセル全体になるようにする。
そして、「.contentShape(Rectangle())」を設定することで、Listのタップ領域をセル全体にする。