質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1819閲覧

【SwiftUI】一定時間経過後にバイブレーションを作動させる方法

Koya

総合スコア1

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2022/08/15 08:17

前提

SwiftUIでストップウォッチアプリを作成しようとしています。
計測開始ボタンを押してからの経過秒数を画面に表示しています。
経過秒数が30秒たったところでバイブレーションを作動させたいと思っているのですが、コードの書き方が分からず困っています。

実現したいこと

ここに実現したいことを箇条書きで書いてください。

  • 30秒経過時にバイブレーションを作動させる

発生している問題・エラーメッセージ

30秒経過時のバイブレーションをどのようにコードに落とし込めば良いか分かりません。 現状のコードでストップウォッチの機能は完成しています。 また、バイブレーションが作動するかの一時的な確認として、ボタンを押したらバイブレーション作動させるというコードは書けました。 あとはボタンを押さずとも、30秒経過したら自動でバイブレーションにしたいと思っています。

該当のソースコード

SwiftUI

1import SwiftUI 2import AudioToolbox 3 4struct ContentView: View { 5 6 @ObservedObject var managerClass: ManagerClass = ManagerClass() 7 8 //触覚バイブレーション 9 let UINFG = UINotificationFeedbackGenerator() 10 11 var body: some View { 12 ZStack { 13 //背景色 14 Color.black 15 .ignoresSafeArea() 16 17 VStack(spacing: 100) { 18 HStack { 19 Button(action: { 20 AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) {} 21 }) { 22 Text("バイブレーションテスト") 23 } 24 } 25 //時間表示の部分 26 ZStack { 27 managerClass.secondsElapsed < 45.0 ? 28 Text(String(format: "%02d", Int(managerClass.secondsElapsed))) 29 .font(Font(UIFont.monospacedDigitSystemFont(ofSize: 100, weight: .light))) 30 .foregroundColor(.white) 31 : 32 Text("finish") 33 .font(.largeTitle) 34 .foregroundColor(.white) 35 36 //タイマー背景 37 Circle() 38 .trim(from: 0.0, to: 1.0) 39 .stroke(Color.gray, style: StrokeStyle(lineWidth: 10, lineCap: .round)) 40 .frame(width: timerDiameter, height: timerDiameter) 41 .rotationEffect(.init(degrees: -90)) 42 43 //タイマー前面 44 managerClass.secondsElapsed < 45 ? 45 Circle() 46 .trim(from: 0.0, to: managerClass.secondsElapsed / 60.0) 47 .stroke(Color.yellow, style: StrokeStyle(lineWidth: 10, lineCap: .round)) 48 .frame(width: timerDiameter, height: timerDiameter) 49 .rotationEffect(.init(degrees: -90)) 50 : 51 Circle() 52 .trim(from: 0.0, to: 0.75) 53 .stroke(Color.red, style: StrokeStyle(lineWidth: 10, lineCap: .round)) 54 .frame(width: timerDiameter, height: timerDiameter) 55 .rotationEffect(.init(degrees: -90)) 56 } 57 58 //ボタンの部分 59 if managerClass.isTimerRunning { 60 withAnimation { 61 HStack { 62 Spacer() 63 Button(action: { 64 managerClass.reset() 65 self.UINFG.notificationOccurred(.success) 66 }) { 67 Image(systemName: "arrow.left.to.line.circle.fill") 68 .font(.system(size: buttonSize)) 69 .foregroundColor(.white) 70 } 71 Spacer() 72 Button(action: { 73 managerClass.stop() 74 self.UINFG.notificationOccurred(.success) 75 }) { 76 Image(systemName: "pause.circle.fill") 77 .font(.system(size: buttonSize)) 78 .foregroundColor(.white) 79 80 } 81 Spacer() 82 } 83 } 84 } else { 85 withAnimation { 86 HStack { 87 Spacer() 88 Button(action: { 89 managerClass.reset() 90 self.UINFG.notificationOccurred(.success) 91 }) { 92 Image(systemName: "arrow.left.to.line.circle.fill") 93 .font(.system(size: buttonSize)) 94 .foregroundColor(.white) 95 } 96 Spacer() 97 Button(action: { 98 managerClass.start() 99 self.UINFG.notificationOccurred(.success) 100 }) { 101 Image(systemName: "play.circle.fill") 102 .font(.system(size: buttonSize)) 103 .foregroundColor(.white) 104 } 105 Spacer() 106 } 107 } 108 } 109 } 110 } 111 } 112} 113 114class ManagerClass: ObservableObject { 115 //経過時間 116 @Published var secondsElapsed: Double = 0.0 117 //タイマーが動いてる状態がtrue、止まっている状態がfalse 118 @Published var isTimerRunning: Bool = false 119 120 var timer = Timer() 121 122 func start() { 123 isTimerRunning = true 124 timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true){ timer in 125 self.secondsElapsed += 0.01 126 } 127 } 128 func stop() { 129 isTimerRunning = false 130 timer.invalidate() 131 } 132 func reset() { 133 secondsElapsed = 0.0 134 } 135 136} 137 138//画面の幅 139let screenWidth: CGFloat = UIScreen.main.bounds.width 140 141//タイマーの直径 142let timerDiameter: CGFloat = screenWidth * 0.8 143 144//ボタンのサイズ 145let buttonSize: Double = 80.0 146 147struct ContentView_Previews: PreviewProvider { 148 static var previews: some View { 149 ContentView() 150 } 151} 152

試したこと

func start()を以下のように書いて試しましたが、バイブレーションは作動しませんでした。

func start() {
isTimerRunning = true
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true){ timer in
self.secondsElapsed += 0.01
}
if self.secondsElapsed == 30 {
AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) {}
}
}

補足情報(FW/ツールのバージョンなど)

MacBookのXCodeでコーディングしています。
バイブレーションの作動確認は実機のiPhoneXSをつないで行っています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

試されたコードは問題点が2点あります。

1つは、secondsElapsedが30になったか判定する処理がタイマーのクロージャの外で行われているため、タイマー起動直後の1回しか動作していないこと。
もう1つは、secondsElapsedがDouble型で0.01秒に1回0.01加えていく処理になっており、Double型の丸め誤差によって、0.01を30回加えた結果が30ちょうどにならないこと。

次のように、タイマーのクロージャの中で30.0以上かつ30.01未満で判定すればとりあえずうまくいくと思いますが、もし30秒以外の別の秒数を判定したい場合は、指定値以上かつ指定値+0.01未満の判定では0.01を加えた結果が丁度その範囲に入らずうまくいかない場合があるかもしれません。きちんとやるならバイブレーション済みのフラグを追加して、secondsElapsedが30.0以上かつバイブレーション済みフラグがバイブレーション未の時にバイブレーションするという判定にした方が良いと思います。

swift

1 func start() { 2 isTimerRunning = true 3 timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true){ timer in 4 self.secondsElapsed += 0.01 5 print(self.secondsElapsed) 6 if self.secondsElapsed >= 30.0 && self.secondsElapsed < 30.01 { 7 AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) {} 8 } 9 } 10 }

上記のコードでは print(self.secondsElapsed) を入れて、実際にsecondsElapsedの値がどう変化するかをprintしていますので、動作させてprintの結果を確認してみてください。

タイマーの精度が1秒でいいなら、0.01秒に一回タイマーを動作させるのではなく、1秒に1回の動作にして、secondsElapsedの型をDoubleではなくIntにすれば、単純に if self.secondsElapsed == 30 { の判定で正しく動作すると思います。

投稿2022/08/15 23:05

TakeOne

総合スコア6299

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Koya

2022/08/16 09:31

ご回答ありがとうございます。 TakeOneさんにいただいたコードでバイブレーションを動作させることができました。 ご指摘いただいた問題点2つについて、勉強になりました。 たしかにsecondsElapsedが30になったか判定する処理はカッコの内側に入れとかないといけないですし、Double型で==30にしていたのは間違いでした。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問