加速度センサーを用いた iOSアプリを開発して遊んでみた
こんにちは、エンジニアのYuito Hayashiです。
みなさんは「グルージェントフロー(Gluegent Flow)」と「共有アドレス帳」にスマートフォンアプリがあることをご存じですか?
最近、私は業務でこれらのiOSアプリの開発保守などに携わっています。
そのため、iOSアプリによく触れているので今回はちょっとしたiOSアプリを開発して遊んで、それについて記事にしていこうと思います。

開発環境
開発環境はXcode 16.4で言語はSwift 5を使用します。
また、インターフェースはStoryboardではなく、SwiftUIで開発することにしました。
どんなアプリを作ろうか考えたのですが、せっかくiPhoneには高性能な様々なセンサーが搭載されているので、今回は加速度センサーを用いたアプリを作成してみようと思います。
加速度の値を取得してみる
まずは、加速度の値を取得し、表示させるiOSアプリを作ってみようと思います。
加速度はCoreMotionというフレームワークに含まれるCMMotionManagerクラスを使うことで取得することができます。
以下のコードから取得した3軸加速度の値を画面に表示させることができます。
import SwiftUI
import CoreMotion
class MotionManager: ObservableObject {
private var phoneMotion = CMMotionManager()
@Published var x: Double = 0.0
@Published var y: Double = 0.0
@Published var z: Double = 0.0
func start() {
phoneMotion.startAccelerometerUpdates(to: .main) { [weak self] data, _ in
guard let data else { return }
self?.x = data.acceleration.x
self?.y = data.acceleration.y
self?.z = data.acceleration.z
}
}
func stop() {
phoneMotion.stopAccelerometerUpdates()
}
}
struct ContentView: View {
@StateObject private var motion = MotionManager()
var body: some View {
VStack {
Text(String(format: "x軸: %.3f", motion.x))
Text(String(format: "y軸: %.3f", motion.y))
Text(String(format: "z軸: %.3f", motion.z))
}
.font(.system(size: 28, design: .rounded))
.onAppear { motion.start() }
.onDisappear { motion.stop() }
}
}
以下が実行画面になります。
iPhoneを傾けるとリアルタイムで数値が変わるのがわかります。

スマホの落下を検知してみる
次に、加速度の値を用いてスマホの落下を検知してみようと思います。
自由落下すると合計の加速度がほぼ0になるはずなので、合計加速度の値が0.1以下になった間隔が0.2秒を超えたときスマホが落下していると判定することにしてみます。
そして、落下と判定されたらスマホをバイブレーションさせて、アラートを表示させるようにしてみたいと思います。
スマホのバイブレーションはAudioToolboxフレームワークを使うことで振動させることができます。
以下のコードからスマホの落下検出ができます。(コードは一部省略してます。)
import AudioToolbox
struct ContentView: View {
@StateObject private var motion = MotionManager()
@State private var showAlert = false
@State private var lowGStartDate: Date? = nil
private let timer = Timer.publish(every: 1.0 / 120.0, on: .main, in: .common).autoconnect()
private let thresholdG: Double = 0.10
private let minDuration: TimeInterval = 0.20
var body: some View {
VStack {
Text(String(format: "x軸: %.3f", motion.x))
Text(String(format: "y軸: %.3f", motion.y))
Text(String(format: "z軸: %.3f", motion.z))
}
.font(.system(size: 28, design: .rounded))
.onAppear { motion.start() }
.onDisappear { motion.stop() }
.onReceive(timer, perform: { _ in
detectFall(x: motion.x, y: motion.y, z: motion.z)
})
.alert("お前今落としただろ", isPresented: $showAlert) {
Button("すみません...") { showAlert = false }
}
}
func detectFall(x: Double, y: Double, z: Double) {
let totalG = sqrt(x*x + y*y + z*z)
if totalG < thresholdG {
if lowGStartDate == nil {
lowGStartDate = Date()
} else if Date().timeIntervalSince(lowGStartDate!) > minDuration {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
showAlert = true
lowGStartDate = nil
}
} else {
lowGStartDate = nil
}
}
}
以下が実行画面になります。
実際にスマホを空中で手放してキャッチしてみたのですが、うまく動作できていました。

スマホを傾けることで画像を動かしてみる
次は、加速度を使って画像を動かす物理シミュレーションっぽいアプリを作ってみようと思います。
画面端に画像が当たったら、ランダムに画像の色を変更してスマホを振動させます。
そして、画像を跳ね返すようにしてみようと思います。
画像は、加速度の値を使用して、座標を更新させることで動かします。
注意点として、iPhoneの座標は中央が原点ではなく、左上が原点になります。
x軸は右に行くほどプラス、y軸は下に行くほどプラスになります。
以下のコードからスマホを傾けることで画像を動かすことができます。(コードは一部省略してます。)
また、画像はAssetsに配置する必要があります。
struct ContentView: View {
@StateObject private var motion = MotionManager()
@State private var imagePosition: CGPoint = .zero
@State private var vx: CGFloat = 0
@State private var vy: CGFloat = 0
@State private var tint = Color.orange
private let timer = Timer.publish(every: 1.0 / 120.0, on: .main, in: .common).autoconnect()
private let imageSize: CGFloat = 100
private let restitution: CGFloat = 0.5
var body: some View {
ZStack {
VStack {
Text(String(format: "x軸: %.3f", motion.x))
Text(String(format: "y軸: %.3f", motion.y))
Text(String(format: "z軸: %.3f", motion.z))
}
.font(.system(size: 28, design: .rounded))
GeometryReader { geometry in
Image("flow-icon")
.renderingMode(.template)
.resizable()
.frame(width: imageSize, height: imageSize)
.position(imagePosition)
.foregroundStyle(tint)
.onAppear {
imagePosition = CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2)
motion.start()
}
.onDisappear { motion.stop() }
.onReceive(timer, perform: { _ in
imagePosition = nextPosition(current: imagePosition, in: geometry.size)
})
}
}
}
func nextPosition(current: CGPoint, in size: CGSize) -> CGPoint {
vx += CGFloat(motion.x)
vy -= CGFloat(motion.y)
var newX = current.x + vx
var newY = current.y + vy
let half = imageSize / 2
if newX <= half || newX >= size.width - half {
vx *= -restitution
newX = min(max(newX, half), size.width - half)
hitEffect()
}
if newY <= half || newY >= size.height - half {
vy *= -restitution
newY = min(max(newY, half), size.height - half)
hitEffect()
}
return CGPoint(x: newX, y: newY)
}
func hitEffect() {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
tint = Color(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1)
)
}
}
以下が実行画面になります。
画面端に当たると色が変わるのはDVDのスクリーンセーバーみたいな感じですね。

さいごに
今回は加速度センサーを使ったiOSアプリを開発して遊んでみました。
iPhoneのほかにも、AirPodsやApple Watchのような身近なデバイスにも加速度センサーが搭載されています。
次は、これらのデバイスと連携したアプリも作ってみたいです。
最後に、「グルージェントフロー(Gluegent Flow)」と「共有アドレス帳」にスマーフォンアプリがあることを知らなかった、使っていないという方がいましたら是非ダウンロードして使ってみてください!
最後まで読んでいただきありがとうございました!
(Yuito Hayashi)