antk
@antk (あんどうた)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【Swift】ボタンを押しても処理を重複させないようにしたいが、処理が終わると再度処理が始まってしまう

Q&A

Closed

解決したいこと

ボタンを押したとき、メソッドの処理が終わるまで再度ボタンを押しても処理を重複させないようにしたいんですが、処理が終わると再度処理が始まってしまいます。
ここでは button.isHidden = true でボタンを消す方法を試していますが、頭に書いてるにも関わらず、メソッドに書かれた処理が終わってから有効になってしまいます。

該当するソースコード

どんな実装なのか、詳細は下記の記事です。
https://qiita.com/antk/items/d0dc6a6c9ff68747dd6c

import UIKit
import AudioToolbox

class ViewController: UIViewController {

  @IBOutlet weak var button: UIButton!

  override func viewDidLoad() {
    super.viewDidLoad()

  }

  @IBAction func actionButton(_ sender: UIButton) {

    button.isHidden = true
    repeater()
  }

  func repeater() {

    let now = Date()
    let time = Calendar.current.dateComponents([.hour, .minute], from: now)

    // hour を12時間で丸める
    let hour = time.hour! % 12
    let quarter = time.minute! / 15
    let minute = time.minute! % 15

    // 低音ゴング
    let lowGong:SystemSoundID = 1054
    // 高音ゴング
    let highGong:SystemSoundID = 1013

    // 第一ゴング
    for _ in 0 ..< hour {
      print("dong")
      AudioServicesPlaySystemSound(lowGong)

      // 0.5秒待つ
      Thread.sleep(forTimeInterval: 0.5)
    }

    // 第二ゴングが鳴る場合遅延処理
    if quarter > 0 {
      Thread.sleep(forTimeInterval: 0.7)
    }

    // 第二ゴング
    for _ in 0 ..< quarter {
      print("ding-dong")
      AudioServicesPlaySystemSound(highGong)
      // 0.4秒待つ
      Thread.sleep(forTimeInterval: 0.4)
      AudioServicesPlaySystemSound(lowGong)

      // 0.5秒待つ
      Thread.sleep(forTimeInterval: 0.5)
    }

    // 第三ゴングが鳴る場合遅延処理
    if minute > 0 {
      Thread.sleep(forTimeInterval: 0.7)
    }

    // 第三ゴング
    for _ in 0 ..< minute {
      print("ding")
      AudioServicesPlaySystemSound(highGong)

      // 0.5秒待つ
      Thread.sleep(forTimeInterval: 0.5)
    }
  }
}

自分で試したこと

メンバ変数に var isPlaing: Bool = false を設置する方法や isEnabled を使う方法も試してみましたがうまく行きませんでした。
これらのパターンの場合、頭でこのメソッド自体を無効にしておいて、要所々々で print を使って無効になっているかを試しました。この場合、Bool値で無効になっているのを確認はしたんですが、やはりボタンを複数回押すと処理が複数回スタックに積まれてしまいます。

0

2Answer

swiftはまったく知らないんですが回答がないようなので一般論として書きます。
isHiddenやisEnabledのGUIコントロールのプロパティが実際に反映されるのはイベントハンドラの処理が終わった後です。画面の更新などで比較的重い処理になる可能性がある上に、イベントハンドラの連鎖でさらに書き換えられる可能性があるため一通りのイベント処理が終わってからプロパティの内容を反映します。
その間にもイベントが発生すればそれらのイベントはキューに貯めていきます。(取りこぼしを防ぐなどの理由)
そんなわけでイベントハンドラの処理は、可能な限りすぐに終わらせるのが望ましいです。

イベントをきっかけに時間のかかる処理を行いたい時には「非同期処理」を行います。

  1. イベントハンドラ開始
  2. isHiddenやisEnabledを設定したいならここで行う
  3. 必要なパラメータを参照し、非同期処理を開始
  4. イベントハンドラ終了(これ以降はisHiddenやisEnabledがちゃんと反映される)
  5. 必要な処理を非同期で実行
  6. 非同期処理を完了

awaitが使えると楽なんですが、swiftではまだ実装されていない(将来のバージョンで対応予定)のようなので、非同期処理のやりかたについては自力でなんとか調べてみてください。

あと非同期処理は別スレッドで行われるため、GUIコントロールのプロパティを書き換えるような処理はメインスレッドで行うようにする必要があります。
(ここらへんもswiftだとどう書けばいいのかいまいちわかりませんでした。でも可能ではあります)

1Like

Comments

  1. @antk

    Questioner

    ありがとうございます!
    swiftに限らない話だったんですね。。勉強になります

UI描画はメインスレッドで行いますが、Thread.sleepを使うとメインスレッドが停止してしまうので、button.isHidden = trueの反映も遅れてしまいます。
また、ボタンを連打した場合isHidden・isEnabledなどで押せなくなるまでに何回か押せてしまうことがあります。そのためvar isPlaing: Bool = falseを使う方法がよいかと思います。

一例ですがDispatchQueue.global()を使い処理を別スレッドで行うことでこの問題を解決できるかと思います。

    private var isPlaying = false

    @IBAction func actionButton(_ sender: UIButton) {
        if isPlaying { return }
        isPlaying = true
        repeater()
    }

    func repeater() {
        DispatchQueue.global().async {
            let now = Date()
            let time = Calendar.current.dateComponents([.hour, .minute], from: now)

            // hour を12時間で丸める
            let hour = time.hour! % 12
            // 60分を15分単位に分割した数
            let quarter = time.minute! / 15

            中略

            // 第三ゴング
            for _ in 0 ..< minute {
              print("ding")
              AudioServicesPlaySystemSound(highGong)

              // 0.5秒待つ
              Thread.sleep(forTimeInterval: 0.5)
            }

            self.isPlaying = false
        }
    }
1Like

Comments

  1. @antk

    Questioner

    ありがとうございます😭
    `DispatchQueue.global()` は知りませんでした。。
    いただいたコードでバッチリ理想的な挙動になりました。重ね重ねありがとうございます!

Your answer might help someone💌