2
Help us understand the problem. What are the problem?

posted at

updated at

【iOS】Alertでradiobuttonを使った選択式の表示を出す

はじめに

フィルター等をかけたい時にradiobuttonを使った選択式のViewを作る必要があり作成しました。

独自のViewを作ればどうとでも作れますが、
なるべくiOSのフレームワーク内で作成した方が統一感が出ると思いAlert内で作成しました。

基本的にはAlert内に独自で作ったViewをaddSubviewするだけです。

開発環境

  • iOS 15.2
  • Xcode 13.3.1
  • Swift 5.6

完成画面

以下のようにAlert内で選択出来るようなradiobuttonを配置します。
どれかひとつしか選択出来ないようにします。

radio_alert2.gif

構成

  • Alert内のViewに独自に作ったViewをAddsubviewします。
  • 独自に作るViewはUITableViewです。
  • radiobuttonなどは独自のtableViewCellを作成して配置します。

ソースコード

UITableViewCell

image.png

  • 左にUILabel、真ん中にImageView、右側にUIButtonを配置。
  • UIButtonはSFSymbolのcircleの画像を設定。
  • 選択された時の青い丸はImageVIewで、SFSymbolのcircle.fillです。
import Foundation
import UIKit

//buttonTupをViewControllerに返す用
protocol FilterCellDelegate:AnyObject {
    func didTapButton(cell: FilterCell)
}

class FilterCell : UITableViewCell{
    
    weak var delegate:FilterCellDelegate?
    //左側のLabel
    let label = UILabel()
    //中央の画像のImageView
    let RSSIImageView = UIImageView()
    //右端のradioButtonの丸
    let radioButton = UIButton()
    //選択された時の青丸表示用のImageView
    let selectedImageView = UIImageView()
    
    static let reuseID = "FilterCell"
    static let rowHeight:CGFloat = 44

    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setup()
        layout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension FilterCell{
    //部品の設定
    private func setup(){
        contentView.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = .preferredFont(forTextStyle: .body)
        label.textColor = .label
        label.text = "RSSI > -62"
        
        contentView.addSubview(RSSIImageView)
        RSSIImageView.image = UIImage.init(systemName: "dot.radiowaves.up.forward")
        RSSIImageView.translatesAutoresizingMaskIntoConstraints = false
        RSSIImageView.tintColor = .label
        
        contentView.addSubview(radioButton)
        radioButton.translatesAutoresizingMaskIntoConstraints = false
        radioButton.setImage(UIImage.init(systemName: "circle"), for: .normal)
        radioButton.imageView?.contentMode = .scaleAspectFit
        radioButton.contentHorizontalAlignment = .fill // オリジナルの画像サイズを超えて拡大(水平)
        radioButton.contentVerticalAlignment = .fill // オリジナルの画像サイズを超えて拡大(垂直)
        radioButton.tintColor = .label
        radioButton.addTarget(self, action: #selector(didTupRadioButton), for: .primaryActionTriggered)
        
        contentView.addSubview(selectedImageView)
        selectedImageView.translatesAutoresizingMaskIntoConstraints = false
        selectedImageView.image = UIImage.init(systemName: "circle.fill")
        selectedImageView.tintColor = .systemBlue
        
        backgroundColor = .clear
        
    }
    //レイアウト
    private func layout(){
        
        //label
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 2),
            label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
        ])
        
        //ImageView
        NSLayoutConstraint.activate([
            RSSIImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            RSSIImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
        ])
        
        //radioButton
        NSLayoutConstraint.activate([
            contentView.trailingAnchor.constraint(equalToSystemSpacingAfter: radioButton.trailingAnchor, multiplier: 2),
            radioButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            //circleのサイズを少し大きめにするためButtonのサイズを大きくする
            radioButton.widthAnchor.constraint(equalToConstant: 30),
            radioButton.heightAnchor.constraint(equalToConstant: 30),
        ])
        
        //selectedImageView
        NSLayoutConstraint.activate([
            selectedImageView.centerXAnchor.constraint(equalTo: radioButton.centerXAnchor),
            selectedImageView.centerYAnchor.constraint(equalTo: radioButton.centerYAnchor)
        ])
        
    }
}

extension FilterCell{
    //radioButtonがTupされた時のコールバック用
    @objc func didTupRadioButton(sender:UIButton){
        delegate?.didTapButton(cell: self)
    }
}

ViewControllerでAlert内にTableViewを配置

  • 設置したButtonを押下するとAlertを表示
  • Alert内にTableViewをaddSubView
  • Cellのボタン押下時のeventをdelegateで取得してradiobuttonの表示:非表示を切り替える。
import Foundation
import UIKit

class ViewController:UIViewController{

    //Filterのセル情報
    var filterCellSettings: [(filterType: String, imageAlpha: Double,selected:Bool)] = [
                ("No filter",0,true),
                ("RSSI > -62",1,false),
                ("RSSI > -74",0.7,false),
                ("RSSI > -86",0.5,false)]
    //AlertにセットするTableView
    var filterAlertlCustumTableView  = UITableView()
    //Alert表示用のButton
    let button = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        style()
        layout()
    }
    
}

extension ViewController{
    
    func style(){
        //buttonの設定
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Alart", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(tupButton), for: .primaryActionTriggered)
    }
    
    func layout(){
        //Buttonを配置
        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo:view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo:view.centerYAnchor)
        ])
        
    }
    
}

extension ViewController{
    //Button押下時の処理
    @objc func tupButton(){
        //alertの高さはmessageに改行を入れることで調整
        let alert: UIAlertController = UIAlertController(title: "Select filter type", message: "\n\n\n\n\n\n\n\n\n\n\n", preferredStyle:  .alert)

        let defaultAction: UIAlertAction = UIAlertAction(title: "OK", style: .default, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("OK")
        })
        let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: .cancel, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("Cancel")
        })
        alert.addAction(cancelAction)
        alert.addAction(defaultAction)
        //datasouceをセット
        filterAlertlCustumTableView.dataSource = self
        filterAlertlCustumTableView.backgroundColor = UIColor.clear
        //cellを登録
        filterAlertlCustumTableView.register(FilterCell.self, forCellReuseIdentifier: FilterCell.reuseID)
        //cellの高さをセット
        filterAlertlCustumTableView.rowHeight = FilterCell.rowHeight
        //スクロールを無効化
        filterAlertlCustumTableView.isScrollEnabled = false
        //tableViewっぽさを無くすために線を透明にして見えなくする
        filterAlertlCustumTableView.separatorColor = .clear
        //alertのviewにTableViewをセット
        alert.view.addSubview(self.filterAlertlCustumTableView)
        filterAlertlCustumTableView.translatesAutoresizingMaskIntoConstraints = false
        //tableViewのレイアウト:left、rightはViewに合わせて、bottomは「OK」などが見えるように50pt間を空ける。高さはcellの高さをカウント数だけ掛ける。
        NSLayoutConstraint.activate([
            self.filterAlertlCustumTableView.leftAnchor.constraint(equalTo: alert.view.leftAnchor),
            self.filterAlertlCustumTableView.rightAnchor.constraint(equalTo: alert.view.rightAnchor),
            self.filterAlertlCustumTableView.bottomAnchor.constraint(equalTo: alert.view.bottomAnchor, constant: -50),
            self.filterAlertlCustumTableView.heightAnchor.constraint(equalToConstant: FilterCell.rowHeight * CGFloat(self.filterCellSettings.count))
        ])
        //alertの表示
        self.present(alert, animated: true, completion: nil)

    }
}

extension ViewController:UITableViewDataSource{
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: FilterCell.reuseID, for: indexPath) as! FilterCell
        cell.selectionStyle = .none
        cell.tag = indexPath.row
        cell.delegate = self
        cell.label.text = filterCellSettings[indexPath.row].filterType
        cell.RSSIImageView.alpha = filterCellSettings[indexPath.row].imageAlpha
        cell.selectedImageView.isHidden = !filterCellSettings[indexPath.row].selected
        return cell
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return filterCellSettings.count
    }

    
}
//Cellのボタンが押された時の処理
extension ViewController:FilterCellDelegate{
    func didTapButton(cell: FilterCell) {
        print("did tup cell \(cell.tag)")
        //選択したcellだけradiobuttonを有効化
        for num in 0...filterCellSettings.count - 1{
            if num == cell.tag{
                filterCellSettings[num].selected = true
            }else{
                filterCellSettings[num].selected = false
            }
        }
        filterAlertlCustumTableView.reloadData()
    }
}

最後に

果たして需要があるのか疑問ですが、
alertのViewにCustomViewをセットすれば大体それっぽく表示することは可能だと思います。
あとはOKボタンを押した時にradioButtonが有効化されている項目を使えばFilterなどで使えると思います。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
2
Help us understand the problem. What are the problem?