Commit 2f5d6433d2
Unsigned
Layout: unified · split
DayByDay.xcodeproj/project.pbxproj +4 −4
| @@ -338,7 +338,7 @@ | |||
| 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
| 339 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; | 339 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; |
| 340 | CODE_SIGN_STYLE = Automatic; | 340 | CODE_SIGN_STYLE = Automatic; |
| 341 | CURRENT_PROJECT_VERSION = 6; | 341 | CURRENT_PROJECT_VERSION = 7; |
| 342 | DEVELOPMENT_TEAM = ZCNAX3VL9D; | 342 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 343 | ENABLE_PREVIEWS = YES; | 343 | ENABLE_PREVIEWS = YES; |
| 344 | GENERATE_INFOPLIST_FILE = YES; | 344 | GENERATE_INFOPLIST_FILE = YES; |
| @@ -353,7 +353,7 @@ | |||
| 353 | "$(inherited)", | 353 | "$(inherited)", |
| 354 | "@executable_path/Frameworks", | 354 | "@executable_path/Frameworks", |
| 355 | ); | 355 | ); |
| 356 | MARKETING_VERSION = 1.3.2; | 356 | MARKETING_VERSION = 1.3.3; |
| 357 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DayByDay; | 357 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DayByDay; |
| 358 | PRODUCT_NAME = "$(TARGET_NAME)"; | 358 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 359 | STRING_CATALOG_GENERATE_SYMBOLS = YES; | 359 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -372,7 +372,7 @@ | |||
| 372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; | 372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
| 373 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; | 373 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; |
| 374 | CODE_SIGN_STYLE = Automatic; | 374 | CODE_SIGN_STYLE = Automatic; |
| 375 | CURRENT_PROJECT_VERSION = 6; | 375 | CURRENT_PROJECT_VERSION = 7; |
| 376 | DEVELOPMENT_TEAM = ZCNAX3VL9D; | 376 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 377 | ENABLE_PREVIEWS = YES; | 377 | ENABLE_PREVIEWS = YES; |
| 378 | GENERATE_INFOPLIST_FILE = YES; | 378 | GENERATE_INFOPLIST_FILE = YES; |
| @@ -387,7 +387,7 @@ | |||
| 387 | "$(inherited)", | 387 | "$(inherited)", |
| 388 | "@executable_path/Frameworks", | 388 | "@executable_path/Frameworks", |
| 389 | ); | 389 | ); |
| 390 | MARKETING_VERSION = 1.3.2; | 390 | MARKETING_VERSION = 1.3.3; |
| 391 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DayByDay; | 391 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DayByDay; |
| 392 | PRODUCT_NAME = "$(TARGET_NAME)"; | 392 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 393 | STRING_CATALOG_GENERATE_SYMBOLS = YES; | 393 | 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 @@ | |||
| 4 | // | 4 | // |
| 5 | 5 | ||
| 6 | import AVFoundation | 6 | import AVFoundation |
| 7 | import UIKit | ||
| 7 | 8 | ||
| 8 | /// Wrapper around AVSpeechSynthesizer that selects the highest-quality | 9 | /// Wrapper around AVSpeechSynthesizer that selects the highest-quality |
| 9 | /// en-US neural voice available on the device. Falls back gracefully if | 10 | /// en-US neural voice available on the device. Falls back gracefully if |
| @@ -13,6 +14,7 @@ final class SpeechSynthesizer { | |||
| 13 | 14 | ||
| 14 | private let synthesizer = AVSpeechSynthesizer() | 15 | private let synthesizer = AVSpeechSynthesizer() |
| 15 | private let voice: AVSpeechSynthesisVoice? | 16 | private let voice: AVSpeechSynthesisVoice? |
| 17 | private let haptics = UIImpactFeedbackGenerator(style: .rigid) | ||
| 16 | 18 | ||
| 17 | private init() { | 19 | private init() { |
| 18 | self.voice = Self.bestAvailableVoice() | 20 | self.voice = Self.bestAvailableVoice() |
| @@ -24,6 +26,11 @@ final class SpeechSynthesizer { | |||
| 24 | } | 26 | } |
| 25 | 27 | ||
| 26 | func speak(_ text: String) { | 28 | 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 | |||
| 27 | // Ensure speech plays through the speaker even when the silent switch is on. | 34 | // Ensure speech plays through the speaker even when the silent switch is on. |
| 28 | try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) | 35 | try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) |
| 29 | try? AVAudioSession.sharedInstance().setActive(true) | 36 | try? AVAudioSession.sharedInstance().setActive(true) |