Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions Accelerometer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
0B548E1A2876ED5000EDCDA8 /* MeasurementPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B548E192876ED5000EDCDA8 /* MeasurementPreview.swift */; };
0B6DD8CF2B816BED0076D93A /* ChartEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B6DD8CE2B816BED0076D93A /* ChartEntry.swift */; };
0B7A81602E816FE900399013 /* AlwaysNotEnoughMemoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B7A815F2E816FE900399013 /* AlwaysNotEnoughMemoryView.swift */; };
0B7F6D7E2E92A77F00AE5F38 /* StubMeasurementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B7F6D7D2E92A77200AE5F38 /* StubMeasurementsView.swift */; };
0B801286286DA9C000F4AA85 /* AnimatableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B801285286DA9C000F4AA85 /* AnimatableArray.swift */; };
0B8C3B2A2A99F9FF00488B1E /* Axis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B8C3B292A99F9FF00488B1E /* Axis.swift */; };
0B8C3B2C2A99FC9600488B1E /* Axes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B8C3B2B2A99FC9600488B1E /* Axes.swift */; };
Expand Down Expand Up @@ -144,6 +145,7 @@
0B548E192876ED5000EDCDA8 /* MeasurementPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasurementPreview.swift; sourceTree = "<group>"; };
0B6DD8CE2B816BED0076D93A /* ChartEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartEntry.swift; sourceTree = "<group>"; };
0B7A815F2E816FE900399013 /* AlwaysNotEnoughMemoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlwaysNotEnoughMemoryView.swift; sourceTree = "<group>"; };
0B7F6D7D2E92A77200AE5F38 /* StubMeasurementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubMeasurementsView.swift; sourceTree = "<group>"; };
0B801285286DA9C000F4AA85 /* AnimatableArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableArray.swift; sourceTree = "<group>"; };
0B8C3B292A99F9FF00488B1E /* Axis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Axis.swift; sourceTree = "<group>"; };
0B8C3B2B2A99FC9600488B1E /* Axes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Axes.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -305,6 +307,7 @@
0B24EC3C2862327E00AD3EF1 /* Settings */ = {
isa = PBXGroup;
children = (
0B7F6D7D2E92A77200AE5F38 /* StubMeasurementsView.swift */,
0B36DFC9289A9E85007130D9 /* Settings.swift */,
0B24EC3928622B7100AD3EF1 /* SettingsView.swift */,
0B24EC3D286232A900AD3EF1 /* RefreshRateView.swift */,
Expand Down Expand Up @@ -721,6 +724,7 @@
0B24EC45286247AD00AD3EF1 /* String.swift in Sources */,
0B9FB1552E8F52CA00D70505 /* DebugSamplesView.swift in Sources */,
0BDED2D828983A4D00CAEAC3 /* DateComponents.swift in Sources */,
0B7F6D7E2E92A77F00AE5F38 /* StubMeasurementsView.swift in Sources */,
0B24EC3A28622B7100AD3EF1 /* SettingsView.swift in Sources */,
0B36DFCA289A9E85007130D9 /* Settings.swift in Sources */,
0BA5B7BC2AD319AC004F1808 /* AttitudeAxesSummaryView.swift in Sources */,
Expand Down Expand Up @@ -940,7 +944,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"Accelerometer/Preview Content\"";
DEVELOPMENT_TEAM = 3HG9ZBM8AV;
ENABLE_PREVIEWS = YES;
Expand All @@ -961,6 +965,10 @@
MARKETING_VERSION = 0.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.panandafog.Accelerometer;
PRODUCT_NAME = Acc.elerometer;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand All @@ -974,7 +982,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"Accelerometer/Preview Content\"";
DEVELOPMENT_TEAM = 3HG9ZBM8AV;
ENABLE_PREVIEWS = YES;
Expand All @@ -995,6 +1003,10 @@
MARKETING_VERSION = 0.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.panandafog.Accelerometer;
PRODUCT_NAME = Acc.elerometer;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
148 changes: 124 additions & 24 deletions Accelerometer/Measurements/Measurer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Measurer: ObservableObject {
private let motion = CMMotionManager()

private var settingsSubscription: AnyCancellable?
private var stubTimer: AnyCancellable?

init(settings: Settings) {
self.settings = settings
Expand All @@ -38,17 +39,43 @@ class Measurer: ObservableObject {
if ((self?.motion.isDeviceMotionActive) != nil) {
self?.prepareMotion()
}
if self?.settings.stubMeasurements == true {
self?.restartStubTimer()
}
}

if settings.stubMeasurements {
startStubMeasurements()
}
}

func startAll() {
startDeviceMotion()
startAccelerometer()
startGyro()
startMagnetometer()
startProximity()
if settings.stubMeasurements {
startStubMeasurements()
} else {
startDeviceMotion()
startAccelerometer()
startGyro()
startMagnetometer()
startProximity()
}
}

func stopAll() {
if settings.stubMeasurements {
stubTimer?.cancel()
stubTimer = nil
} else {
stopDeviceMotion()
stopAccelerometer()
stopGyro()
stopMagnetometer()
stopProximity()
}
}

// MARK: — Real sensors

func startDeviceMotion() {
guard !motion.isDeviceMotionActive else {
return
Expand Down Expand Up @@ -97,7 +124,7 @@ class Measurer: ObservableObject {
guard let data = data else {
fatalError("no data")
}

saveData(
axesType: TriangleAxes.self,
measurementType: .acceleration,
Expand Down Expand Up @@ -159,21 +186,13 @@ class Measurer: ObservableObject {
NotificationCenter.default.addObserver(
self,
selector: #selector(proximityDidChange),
name:
name:
UIDevice
.proximityStateDidChangeNotification,
object: UIDevice.current
)
}

func stopAll() {
stopDeviceMotion()
stopAccelerometer()
stopGyro()
stopMagnetometer()
stopProximity()
}

func stopDeviceMotion() {
motion.stopDeviceMotionUpdates()
}
Expand All @@ -194,6 +213,84 @@ class Measurer: ObservableObject {
NotificationCenter.default.removeObserver(self)
}

// MARK: — Stub measurements for emulator

private func startStubMeasurements() {
stubTimer = Timer
.publish(
every: settings.updateInterval,
on: .main,
in: .common
)
.autoconnect()
.sink { [weak self] _ in
self?.emitStubValues()
}
}

private func restartStubTimer() {
stubTimer?.cancel()
startStubMeasurements()
}

private func emitStubValues() {
for type in MeasurementType.allShownCases {
let maxVal = displayableAbsMax[type] ?? 1.0

switch type {

case .acceleration, .userAcceleration, .gravity:
let vx = Double.random(in: -maxVal...maxVal)
let vy = Double.random(in: -maxVal...maxVal)
let vz = Double.random(in: -maxVal...maxVal)

saveData(
axesType: TriangleAxes.self,
measurementType: type,
values: [.x: vx, .y: vy, .z: vz]
)

case .rotationRate:
let vx = Double.random(in: -maxVal...maxVal)
let vy = Double.random(in: -maxVal...maxVal)
let vz = Double.random(in: -maxVal...maxVal)

saveData(
axesType: TriangleAxes.self,
measurementType: type,
values: [.x: vx, .y: vy, .z: vz]
)

case .magneticField:
let vx = Double.random(in: -maxVal...maxVal)
let vy = Double.random(in: -maxVal...maxVal)
let vz = Double.random(in: -maxVal...maxVal)

saveData(
axesType: TriangleAxes.self,
measurementType: type,
values: [.x: vx, .y: vy, .z: vz]
)

case .attitude:
let roll = Double.random(in: -maxVal...maxVal)
let pitch = Double.random(in: -maxVal...maxVal)
let yaw = Double.random(in: -maxVal...maxVal)

saveData(
axesType: AttitudeAxes.self,
measurementType: .attitude,
values: [.roll: roll, .pitch: pitch, .yaw: yaw]
)

default:
break
}
}
}

// MARK: — Shared helpers

func resetAll() {
MeasurementType.allCases.forEach {
reset($0)
Expand All @@ -208,7 +305,11 @@ class Measurer: ObservableObject {
motion.setUpdateInterval(settings.updateInterval)
}

func saveData<AxesType: Axes>(axesType: AxesType.Type, measurementType: MeasurementType, values: [AxeType: AxesType.ValueType]) {
func saveData<AxesType: Axes>(
axesType: AxesType.Type,
measurementType: MeasurementType,
values: [AxeType: AxesType.ValueType]
) {
if observableAxes[measurementType] == nil {
var axes = axesType.zero
if let displayableAbsMax = displayableAbsMax[measurementType] as? AxesType.ValueType {
Expand All @@ -228,14 +329,13 @@ class Measurer: ObservableObject {
}

@objc func proximityDidChange(notification: NSNotification) {
guard let device = notification.object as? UIDevice else { return }
let currentProximityState = device.proximityState

saveData(
axesType: BooleanAxes.self,
measurementType: .proximity,
values: [.bool: currentProximityState]
)
if let device = notification.object as? UIDevice {
saveData(
axesType: BooleanAxes.self,
measurementType: .proximity,
values: [.bool: device.proximityState]
)
}
}
}

Expand Down
31 changes: 29 additions & 2 deletions Accelerometer/Settings/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,53 @@ class Settings: ObservableObject {
}

// MARK: - Debug options
#if DEBUG
var enableAnimations: Bool {
get {
#if DEBUG
return UserDefaults.standard.bool(forKey: Key.enableAnimations.rawValue)
#else
return true
#endif
}
set {
#if DEBUG
objectWillChange.send()
UserDefaults.standard.set(newValue, forKey: Key.enableAnimations.rawValue)
#endif
}
}

var alwaysNotEnoughMemory: Bool {
get {
#if DEBUG
return UserDefaults.standard.bool(forKey: Key.alwaysNotEnoughMemory.rawValue)
#else
return false
#endif
}
set {
#if DEBUG
objectWillChange.send()
UserDefaults.standard.set(newValue, forKey: Key.alwaysNotEnoughMemory.rawValue)
#endif
}
}

var stubMeasurements: Bool {
get {
#if DEBUG
return UserDefaults.standard.bool(forKey: Key.stubMeasurements.rawValue)
#else
return false
#endif
}
set {
#if DEBUG
objectWillChange.send()
UserDefaults.standard.set(newValue, forKey: Key.stubMeasurements.rawValue)
#endif
}
}
#endif
}

// MARK: - ExportDateFormat
Expand Down Expand Up @@ -118,6 +144,7 @@ extension Settings {
#if DEBUG
case enableAnimations = "EnableAnimations"
case alwaysNotEnoughMemory = "AlwaysNotEnoughMemory"
case stubMeasurements = "StubMeasurements"
#endif
case measurementsUpdateInterval = "MeasurementsUpdateInterval"
}
Expand Down
1 change: 1 addition & 0 deletions Accelerometer/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct SettingsView: View {
footer: Text("Debug options")
) {
AnimationsView()
StubMeasurementsView()
AlwaysNotEnoughMemoryView()
DebugSamplesView()
}
Expand Down
34 changes: 34 additions & 0 deletions Accelerometer/Settings/StubMeasurementsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// StubMeasurementsView.swift
// Accelerometer
//
// Created by Andrey Pantyuhin on 05.10.2025.
//

import SwiftUI

#if DEBUG
struct StubMeasurementsView: View {

@EnvironmentObject var settings: Settings

var body: some View {
Toggle(
"Use stub measurements",
isOn: Binding(
get: { settings.stubMeasurements },
set: { settings.stubMeasurements = $0 }
)
)
.tint(.accentColor)
}
}

struct StubMeasurementsViewPreviews: PreviewProvider {
static var previews: some View {
StubMeasurementsView()
.previewLayout(.sizeThatFits)
.environmentObject(Settings())
}
}
#endif
Loading
Loading