Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AlarmKitで実現する 新時代のシステム通知

AlarmKitで実現する 新時代のシステム通知

サードパーティアプリ開発者は長年、Appleの純正アラーム機能に制約を感じてきました。
Silent modeやFocus modeに阻まれ、重要な通知が届かないリスクは、開発者とユーザー双方にとって深刻な問題でした。

iOS 26のAlarmKitは、この技術的障壁をついに解放し、開発者にも革命的なアラーム機能の実装を可能にします。
従来のローカル通知の根本的問題は、デバイス設定への依存性でした。
AlarmKitを使用することで、重要な通知がどんな状況でもユーザーに確実に届くようになります。

AlarmKitの主な革新として以下の特徴が挙げられます。

1)あらゆるシステム設定を突破するアラーム実行により、Silent modeやFocus modeの影響を受けない確実な通知を実現
2)Live Activitiesと連携したDynamic Island統合により、ユーザーの目に留まりやすい視覚的なアラーム表示を提供
3)Apple Watchでの自動同期により、デバイスを問わない一貫したアラーム機能
4)App Intentsによるカスタムアクション実行により、アラーム後の独自ワークフローを組み込み可能

実装デモを行い、料理タイマーアプリを題材に段階的な構築過程をお見せします。
AlarmKitの登場により、開発者はユーザーにとってより信頼性の高いアラーム機能を提供できるようになります。
https://fortee.jp/iosdc-japan-2025/proposal/439dad47-8495-4f7e-8ee0-e244bb1e7667

Avatar for Ryo Tsuzukihashi

Ryo Tsuzukihashi

September 19, 2025
Tweet

More Decks by Ryo Tsuzukihashi

Other Decks in Technology

Transcript

  1. ࣗݾ঺հ ᠃ڮྋ !UTV[VLJ ʮࢥ͍ग़ͱςΫϊϩδʔͰ৽͍͠ମݧΛ૑଄͢Δʯ ݸਓ։ൃؤுͬͯΔ ୅ද࡞඼ ཱྀߦࢥ͍ग़Ϛοϓ ݅ ߴධ ສ%-ཱྀͷࢥ͍ग़Λ஍ਤʹه࿥͢ΔΞϓϦ

    ྲྀΕΔϝϞா ສ%- ϝσΟΞ঺հଟ਺৽ײ֮ͷϝϞΞϓϦ ͍ͭͰ΋λΠϚʔ ਆΞϓϦड৆ -JWF"DUJWJUZରԠͷλΠϚʔΞϓϦ גࣜձࣾ;0;0 ;0;0508/J04։ൃ ྺ࢙͋Δେن໛ΞϓϦΛҰॹʹΑ͍ͯ͘͘͠஥ؒืूத ͜Ε·ͰݸਓͰϦϦʔεͨ͠ΞϓϦ͸ݸ
  2. AlarmKitొ৔ J04ͷֵ໋ ৽࣌୅ͷγεςϜ௨஌ϑϨʔϜϫʔΫ iOS 26 / iPadOS 26ରԠ Silent Mode

    / Focus Modeಥഁ ॏཁͳ௨஌Λ࣮֬ʹϢʔβʔʹಧ͚Δ ΧελϜΞϥʔϜͱλΠϚʔ ΧελϚΠζՄೳͳεέδϡʔϧͱ6* ճݶΓɺ܁Γฦ͠ɺΧ΢ϯτμ΢ϯɺεψʔζػೳ ֵ໋తͳΞϥʔϜػೳ λΠϚʔ ఆظΞϥʔϜ εψʔζ
  3. / / UIଐੑͷఆٛ let stopButton = AlarmButton( text: "͓͸Α͏", textColor:

    Color.blue, systemImageName: "checkmark.circle" ) let alertPresentation = AlarmPresentation.Alert( title: "ࠓ೔͸iOSDCʂى͖ͳ͍ͱ஗ࠁ͢Δͧʂʂʂ", stopButton: stopButton ) / / ͦΕҎ֎ͷݟͨ໨ͷ࡞੒ let attributes = AlarmAttributes( presentation: AlarmPresentation(alert: alertPresentation), metadata: SampleAlarmMetadata(), tintColor: Color.orange ) / / εέδϡʔϧͷఆٛɹྫ: ݻఆ೔࣌ͷΞϥʔϜ let schedule = Alarm.Schedule.fixed(Date().addingTimeInterval(3)) let alarmConfiguration = AlarmManager.AlarmConfiguration<SampleAlarmMetadata> .alarm( schedule: schedule, attributes: attributes ) / / εέδϡʔϧ࣮ߦ _ = try? await AlarmManager.shared.schedule(id: UUID(), configuration: alarmConfiguration) γϯϓϧͳΞϥʔϜ࣮૷ ΞϥʔϜϑϩʔ ΞϥʔϜఆٛ γεςϜ΁ొ࿥ εέδϡʔϧ࣮ߦ Ξϥʔτදࣔ
  4. // UIଐੑͷఆٛ let stopButton = AlarmButton( text: "͓͸Α͏", textColor: Color.blue,

    systemImageName: "checkmark.circle" ) let alertPresentation = AlarmPresentation.Alert( title: "ࠓ೔͸iOSDCʂى͖ͳ͍ͱ஗ࠁ͢Δͧʂʂʂ", stopButton: stopButton ) γϯϓϧͳΞϥʔϜ࣮૷
  5. // εέδϡʔϧͷఆٛɹྫ: ݻఆ೔࣌ͷΞϥʔϜ let schedule = Alarm.Schedule.fixed(Date().addingTimeInterval(10)) let alarmConfiguration =

    AlarmManager.AlarmConfiguration<SampleAlarmMetadata> .alarm( schedule: schedule, attributes: attributes ) γϯϓϧͳΞϥʔϜ࣮૷
  6. Χ΢ϯτμ΢ϯ࣮૷ ͱεψʔζ λΠϚʔػೳ ఆٛ Χ΢ϯτμ΢ϯظؒΛఆٛ Alarm.CountdownDuration preAlert: ॳճΞϥʔτ·Ͱͷ࣌ؒ postAlert: εψʔζ࣌ؒ

    UI Χ΢ϯτμ΢ϯҰ࣌ఀࢭ6*Λఆٛ AlarmPresentation.Countdown Χ΢ϯτμ΢ϯதͷλΠτϧɺҰ࣌ఀࢭϘλϯ ಈ࡞ ηΧϯμϦϘλϯͷಈ࡞ઃఆ secondaryButtonBehavior: .countdown -> εψʔζ΍λΠϚʔ܁Γฦ͠ΛτϦΨʔ -> postAlert
  7. Χ΢ϯτμ΢ϯ࣮૷ λΠϚʔػೳͱεψʔζ let alertContent = AlarmPresentation.Alert( title: "AlarmKitΛ఻͑Δొஃ࣌ؒ", stopButton: .stopButton,

    secondaryButton: .repeatButton, secondaryButtonBehavior: .countdown ) let countdownContent = AlarmPresentation.Countdown(title: "ۓுͷొஃ", pauseButton: .init(text: "ఀࢭ", textColor: .orange, systemImageName: "pause")) let pausedContent = AlarmPresentation.Paused(title: "ۓு࠶։", resumeButton: .init(text: "࠶։", textColor: .blue, systemImageName: "play.fill")) let attributes = AlarmAttributes( presentation: AlarmPresentation( alert: alertContent, countdown: countdownContent, paused: pausedContent ), metadata: CookingData(method: .fry), tintColor: .orange ) let id = UUID() let alarmConfiguration = AlarmConfiguration( countdownDuration: .init(preAlert: 15 * 60, postAlert: 5 * 60), attributes: attributes, secondaryIntent: RepeatIntent(alarmID: id.uuidString) )
  8. Χ΢ϯτμ΢ϯ࣮૷ ·ͣΞϥʔτը໘ let alertContent = AlarmPresentation.Alert( title: “AlarmKitΛ࢖ͬͯΈΔ", stopButton: .stopButton,

    secondaryButton: .repeatButton, secondaryButtonBehavior: .countdown ) SecondaryButtonBehavior .countdown .custom→ࣗ༝ͳΞΫγϣϯ͕ઃఆͰ͖Δ
  9. Χ΢ϯτμ΢ϯ࣮૷ ·ͣΞϥʔτը໘ extension AlarmButton { static var repeatButton: Self {

    AlarmButton( text: "Repeat", textColor: .black, systemImageName: "repeat.circle" ) } }
  10. Χ΢ϯτμ΢ϯ࣮૷ λΠϚʔػೳͱεψʔζ let attributes = AlarmAttributes( presentation: AlarmPresentation( alert: alertContent,

    countdown: countdownContent, paused: pausedContent ), metadata: CookingData(method: .fry), tintColor: .orange )
  11. Χ΢ϯτμ΢ϯ࣮૷ λΠϚʔػೳͱεψʔζ let id = UUID() let alarmConfiguration = AlarmConfiguration(

    countdownDuration: .init(preAlert: 15 * 60, postAlert: 5 * 60), attributes: attributes, secondaryIntent: RepeatIntent(alarmID: id.uuidString) ) try await AlarmManager.shared.schedule( id: id, configuration: alarmConfiguration )
  12. Χ΢ϯτμ΢ϯ࣮૷ λΠϚʔػೳͱεψʔζ struct RepeatIntent: LiveActivityIntent { func perform() throws ->

    some IntentResult { try AlarmManager.shared.countdown(id: UUID(uuidString: alarmID)!) return .result() } static var title: LocalizedStringResource = "Repeat" static var description = IntentDescription("Repeat a countdown") @Parameter(title: "alarmID") var alarmID: String
  13. Live Activities - Dynamic IslandରԠ Dynamic Island / ϩοΫը໘Ͱͷࢹ֮తମݧ Dynamic

    Island දࣔ ϩοΫը໘ ελϯόΠϞʔυ • ΞϥʔϜͷঢ়ଶ (.countdown, .paused) ʹԠͨ͡ϏϡʔΛఏڙ "DUJWJUZ,JUΛར༻ͯ͠ΧελϜ6*Λ࣮૷
  14. ΧελϜϝλσʔλ struct CookingData: AlarmMetadata { let createdAt: Date let method:

    Method? enum Method: String, Codable { case stove case oven var icon: String { switch self { case .stove: "stove" case .oven: "oven" } } } }
  15. Live Activities struct AlarmLiveActivity: Widget { var body: some WidgetConfiguration

    { ActivityConfiguration(for: AlarmAttributes<CookingData>.self) { context in // The Lock Screen presentation. lockScreenView(attributes: context.attributes, state: context.state) } dynamicIsland: { context in // The presentations that appear in the Dynamic Island. DynamicIsland { // The expanded Dynamic Island presentation. DynamicIslandExpandedRegion(.leading) { alarmTitle(attributes: context.attributes, state: context.state) } DynamicIslandExpandedRegion(.trailing) { cookingMethod(metadata: context.attributes.metadata) } DynamicIslandExpandedRegion(.bottom) { bottomView(attributes: context.attributes, state: context.state) } } compactLeading: { // The compact leading presentation. countdown(state: context.state, maxWidth: 44) .foregroundStyle(context.attributes.tintColor) } compactTrailing: { // The compact trailing presentation. AlarmProgressView(cookingMethod: context.attributes.metadata ? . method, mode: context.state.mode, tint: context.attributes.tintColor) } minimal: { // The minimal presentation. AlarmProgressView(cookingMethod: context.attributes.metadata ? . method, mode: context.state.mode, tint: context.attributes.tintColor) } .keylineTint(context.attributes.tintColor) } }
  16. Live Activities struct AlarmLiveActivity: Widget { var body: some WidgetConfiguration

    { ActivityConfiguration(for: AlarmAttributes<CookingData>.self) { context in // The Lock Screen presentation. lockScreenView(attributes: context.attributes, state: context.state) } dynamicIsland: { context in // The presentations that appear in the Dynamic Island. DynamicIsland { // The expanded Dynamic Island presentation. DynamicIslandExpandedRegion(.leading) { alarmTitle(attributes: context.attributes, state: context.state) }
  17. App Intents ΧελϜΞΫγϣϯ Λ૊ΈࠐΉ ΞϥʔϜޙͷಠࣗϫʔΫϑϩʔ App Intent ఀࢭϘλϯɺηΧϯμϦϘλϯͷ྆ํʹରԠ AlarmManager.AlarmConfiguration stopIntent

    secondaryIntent secondaryButtonBehavior: .custom LiveActivityIntent PQFO"QQ8IFO3VO Πϯςϯτ࣮ߦ࣌ʹΞϓϦΛىಈ ىಈޙͷৄࡉϏϡʔදࣔͳͲɺෳࡶͳϫʔΫϑϩʔΛ࣮ݱ ΞϓϦىಈΧελϜॲཧ ϨγϐදࣔͳͲಛఆͷը໘ʹભҠ ΞϥʔϜൃՐ ϢʔβʔʹΞϥʔτ͕දࣔ͞ΕΔ ηΧϯμϦϘλϯλοϓ ΧελϜΞΫγϣϯΛτϦΨʔ "QQ*OUFOU࣮ߦ ϝιου͕ݺͼग़͞ΕΔ
  18. Thank You! ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ AlarmKitͷՄೳੑΛօ͞ΜͷΞϓϦͰͥͻ׆͔͍ͯͩ͘͠͞ ᠃ڮྋ iOS Developer & Creator Twitter(X)

    @tsuzuki817 GitHub tsuzukihashi ࣭͝໰͸DM·ͨ͸ϋογϡλάͰʂ iOSDC Japan 2025 | 2025/09/19 | Track D