krz/daybyday

iOS app for children to learn days of the week, months, and seasons with audio assist. education ios kids swift https://krz.sh/apps/daybyday/

Commit 2f5d6433d2

2f5d6433d2a56eee7c3df95747d4ca0eb5d86df2

parent: 47864264e7

Unsigned

cmc <hello@cleberg.net> · 2026-07-25 21:34 UTC

feat: add haptic feedback on card tap

Fire a rigid impact haptic in SpeechSynthesizer.speak(), which every
card tap routes through, giving consistent tactile feedback across all
categories.

Layout: unified · split

DayByDay.xcodeproj/project.pbxproj +4 −4
@@ -338,7 +338,7 @@
338338 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
339339 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
340340 CODE_SIGN_STYLE = Automatic;
341 CURRENT_PROJECT_VERSION = 6;
341 CURRENT_PROJECT_VERSION = 7;
342342 DEVELOPMENT_TEAM = ZCNAX3VL9D;
343343 ENABLE_PREVIEWS = YES;
344344 GENERATE_INFOPLIST_FILE = YES;
@@ -353,7 +353,7 @@
353353 "$(inherited)",
354354 "@executable_path/Frameworks",
355355 );
356 MARKETING_VERSION = 1.3.2;
356 MARKETING_VERSION = 1.3.3;
357357 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DayByDay;
358358 PRODUCT_NAME = "$(TARGET_NAME)";
359359 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -372,7 +372,7 @@
372372 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
373373 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
374374 CODE_SIGN_STYLE = Automatic;
375 CURRENT_PROJECT_VERSION = 6;
375 CURRENT_PROJECT_VERSION = 7;
376376 DEVELOPMENT_TEAM = ZCNAX3VL9D;
377377 ENABLE_PREVIEWS = YES;
378378 GENERATE_INFOPLIST_FILE = YES;
@@ -387,7 +387,7 @@
387387 "$(inherited)",
388388 "@executable_path/Frameworks",
389389 );
390 MARKETING_VERSION = 1.3.2;
390 MARKETING_VERSION = 1.3.3;
391391 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DayByDay;
392392 PRODUCT_NAME = "$(TARGET_NAME)";
393393 STRING_CATALOG_GENERATE_SYMBOLS = YES;
DayByDay.xcodeproj/project.xcworkspace/xcuserdata/cmc.xcuserdatad/UserInterfaceState.xcuserstate added

Binary file not shown.

DayByDay/SpeechSynthesizer.swift +7
@@ -4,6 +4,7 @@
44//
55
66import AVFoundation
7import UIKit
78
89/// Wrapper around AVSpeechSynthesizer that selects the highest-quality
910/// en-US neural voice available on the device. Falls back gracefully if
@@ -13,6 +14,7 @@ final class SpeechSynthesizer {
1314
1415 private let synthesizer = AVSpeechSynthesizer()
1516 private let voice: AVSpeechSynthesisVoice?
17 private let haptics = UIImpactFeedbackGenerator(style: .rigid)
1618
1719 private init() {
1820 self.voice = Self.bestAvailableVoice()
@@ -24,6 +26,11 @@ final class SpeechSynthesizer {
2426 }
2527
2628 func speak(_ text: String) {
29 // Every speak() call is a card tap, so give a gentle tactile bump
30 // alongside the audio. Prepared just before firing for lowest latency.
31 haptics.prepare()
32 haptics.impactOccurred()
33
2734 // Ensure speech plays through the speaker even when the silent switch is on.
2835 try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
2936 try? AVAudioSession.sharedInstance().setActive(true)