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

肥大化しがちなアプリの起動経路を整理する

2594ac7ce91fd7d9a3ce71ca7cc2d0c0?s=47 d_date
August 31, 2018

 肥大化しがちなアプリの起動経路を整理する

2018/08/31 iOSDC Japan 2018

2594ac7ce91fd7d9a3ce71ca7cc2d0c0?s=128

d_date

August 31, 2018
Tweet

Transcript

  1. ංେԽ͕ͪ͠ͳ Daiki Matsudate (@d_date) ΞϓϦͷىಈܦ࿏Λ੔ཧ͢Δ iOSDC Japan 2018

  2. Daiki Matsudate @d_date FOLIO Co., Ltd.

  3. ຊ೔ͷςʔϚ ΞϓϦΛىಈͯ͠΋Β͏

  4. ຊ೔ͷςʔϚ ΞϓϦΛىಈͯ͠΋Β͏

  5. ຊ೔ͷςʔϚ ͨ͘͞Μͷىಈܦ࿏ΛͲ͏ࡹ͔͘

  6. ໔੹ࣄ߲

  7. ࠇຐज़΋৽͍͠ઃܭ֓೦΋ ొ৔͠·ͤΜ

  8. Q.ΞϓϦͷىಈܦ࿏ ͍ͭ͘ݴ͑·͔͢ʁ Ϣʔβʔ͔Βݟͯ۠ผͰ͖Δ΋ͷΛΧ΢ϯτ

  9. A. 6Ҏ্͸͋Δ

  10. 1. ௨ৗىಈ

  11. ಛʹͳ͠ AppDelegateͷdidFinishLaunchingΛ௨ΔΑ

  12. 2. ϓογϡ௨஌

  13. ϓογϡ௨஌ • Local Push Notification • Remote Push Notification

  14. ϓογϡ௨஌ • Local Push Notification • Remote Push Notification ~

    iOS 9: AppDelegate iOS 10 ~: UserNotifiaction.framework
  15. 3. Universal Links

  16. Universal Links (Deep Link) • ΞϓϦΠϯετʔϧࡁΈͷ৔߹ • Web → App

    • User Activityѻ͍(URL SchemeʹΑΔભҠͱಉ͡ʣ • activityType͸NSUserActivityTypeBrowsingWeb
  17. Universal Links (Deep Link) • ΞϓϦະΠϯετʔϧͷ৔߹ • Web → App

    Store → App • didFinishLaunching͕ݺ͹ΕΔʢ௨ৗىಈʣ • URLΛ։͍ͯ೚ҙͷϖʔδ΁ʢopenURL)
  18. Universal Links (Deep Link) • ΞϓϦΠϯετʔϧࡁΈͷ৔߹ • ΞϓϦະΠϯετʔϧͷ৔߹ Ϣʔβʔ͔ΒΈΔͱ1ͭͳͷʹɺ࣮૷͸2ύλʔϯ

  19. 4. Spotlight

  20. Spotlight • ݕࡧ͔Βͷىಈ • ݕࡧ݁Ռ (CSSearchableItemActionType) • ΞϓϦ಺ݕࡧʢiOS 10 ~ʣ

    (CSQueryContinuationActionType) • ͲͪΒ΋User Activityѻ͍ʹͳΔ
  21. 5. 3D Touch

  22. 3D Touch ( Home Screen Quick Action ) • AppDelegateͷperformShortcutItem

    • ఆٛͨ͠ϝχϡʔͷ਺͚ͩભҠઌ͕͋Δ
  23. 6.Widget

  24. Widget ( Today Extension) • Widget͔ΒͷΞϓϦຊମͷىಈ • Widget͔ΒopenAppURLͰURL SchemeΛݺΜͰىಈ͢Δ •

    ΞϓϦଆͰ͸openURL͕ݺ͹ΕΔ
  25. ΞϓϦͷىಈܦ࿏ • ௨ৗىಈ • ϓογϡ௨஌ʢLocal / Remoteʣ • σΟʔϓϦϯΫ •

    Core Spotlight (Result Action / Query ) • Home Screen Quick Action • Widget
  26. 7. Siri Shortcut

  27. NDA

  28. Siri Shortcutͷىಈ൑ఆ • User Activityѻ͍ • actionType͸ઃఆ࣌ʹఆٛ • “com.d-date.hogehoge.app” Έ͍ͨͳ΍ͭ(ఆ਺ͳ͠)

  29. ΞϓϦͷىಈܦ࿏ • ௨ৗىಈ • ϓογϡ௨஌ʢLocal / Remoteʣ • σΟʔϓϦϯΫ •

    Core Spotlight (Result Action / Query ) • Home Screen Quick Action • Siri Shortcut • etc…
  30. ΞϓϦͷىಈܦ࿏ App Delegate ௨ৗىಈ ϓογϡ௨஌ʢ ~ iOS 9 ) σΟʔϓϦϯΫ

    Core Spotlight Home Screen Quick Action Siri Shortcut (Intents) User Notification ϓογϡ௨஌ʢiOS 10 ~) etc…
  31. ىಈܦ࿏͸͜ͷ̐ͭͷϝιου ( AppDelegate ) • application:didFinishLaunching • application:openURL • application:continueUserActivity

    • application:performAction
  32. Կ΋ߟ͑ͣʹAppDelegate ʹભҠॲཧΛॻ͍ͯΈΔ

  33. App Delegate import UIKit import UserNotifications import CoreSpotlight @UIApplicationMain class

    AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var tabBarController: UITabBarController? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds) self.tabBarController = UITabBarController() tabBarController.viewControllers = [FirstViewController(), SecondViewController(), ThirdViewController()] window?.rootViewController = tabBarController return true } func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { switch userActivity.activityType { case NSUserActivityTypeBrowsingWeb: guard let url = userActivity.webpageURL else { fatalError("unreachable") } let detailViewController = DetailViewController() detailViewController.identifier = url.lastPathComponent (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) case CSSearchableItemActionType: guard let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String else { fatalError("unreachable")} let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) case CSQueryContinuationActionType: guard let query = userActivity.userInfo?[CSSearchQueryString] as? String else { fatalError("unreachable") } let detailViewController = DetailViewController() detailViewController.identifier = query (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) case "com.d-date.CoordinatorExample.intent": // Siri shortcut let identifier = userActivity.userInfo?["signiture"] as? String let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) default: break } return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { if url.scheme == "coordinator-example-widget" { let identifier = url.lastPathComponent let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) } else if url.scheme == "adjustSchemeExample" { let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) } else if url.scheme == "FirebaseDynamicLinksExmaple" { let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) } } func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { let text = shortcutItem.type.replacingOccurrences(of: bundleIdentifier + ".", with: "").lowercased() switch text { case "a": let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) case "b": let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) case "c": let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) default: break } } } extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let detailViewController = DetailViewController() detailViewController.identifier = identifier (tabBarController?.viewControllers?[0] as? UINavigationController)?.pushViewController(detailViewController, animated: true) completionHandler() } } • ※ιʔείʔυ͸ΠϝʔδͰ͢
  34. έʔεελσΟ • ىಈॲཧ͕ͻͱͭ૿͑ͨͷͰɺ࣮૷͠·ͨ͠ɻɹɹɹɹɹ खಈͰಈ࡞֬ೝͯ͠΋໰୊͋Γ·ͤΜͰͨ͠ • ςετʁॻ͍ͯ·ͤΜ͜͜͠͸ॻ͚ͳ͍Ͱ͢Ͷ • ະϩάΠϯ࣌ʹUniversal Link͔Β։͍ͨ࣌͸ɺͬͪ͜ ͷը໘ʹඈ͹͞ͳ͍ͱɻAppDelegateʹॲཧೖΕ͓ͯ

    ͖·͢Ͷ
  35. ͜͏ͯ͠Fat App Delegate͕ ஀ੜͨ͠ͷͩͬͨ

  36. None
  37. Fat App Delegateͷ໰୊ ίʔυྔ͕ଟ͍

  38. Fat App Delegateͷ໰୊ ίʔυྔ͕ଟ͍

  39. Fat App Delegateͷ໰୊ खಈͰςετ͢Δඞཁ͕͋Δ σάϨ͕ݕ஌Ͱ͖ͳ͍

  40. Q.ίʔυྔ͕ଟ͍͜ͱ͸໰୊ͳͷ͔ʁ A.Ωνϯͱ੹຿ʹ෼ׂ͞Ε͍ͯΕ͹໰୊ͳ͍

  41. Q. खಈͰςετ͢Ε͹ͦΕͰ͍͍͡Όͳ͍Ͱ͔͢ A. ࠷ऴతʹ͸͢Δ͕ɺ͠ͳ͍͍ͯ͘ͱ͜Ζ͸ ͨ͘͠ͳ͍

  42. Q. ࣗಈςετΛ͢ΔΜͰ͔͢ʁ A. ίετ͕ߴ͍ͷͰɺͰ͖Ε͹ͨ͋͘͠Γ· ͤΜ

  43. Q. σάϨ͸ݕ஌Ͱ͖·͔͢ʁ A. ϢχοτςετͰ໢ཏͰ͖͍ͯΕ͹ՄೳͰ͢ɻ

  44. ςετΛॻ͔ͳ͍ཧ༝͕ͳ͘ͳͬͨ

  45. ςετͰ͖Δઃܭ΁

  46. AppDelegateʹ͓͚Δىಈॲཧʹؔ͢Δ੹຿ ભҠઌͷܾఆ τϥοΩϯά ىಈΠϕϯτͷड͚औΓ ભҠ

  47. AppDelegateʹ͓͚Δىಈॲཧʹؔ͢Δ੹຿ ભҠઌͷܾఆ τϥοΩϯά ىಈΠϕϯτͷड͚औΓ ભҠ ͜͜͸ςετՄೳ

  48. AppDelegateʹ͓͚Δىಈॲཧʹؔ͢Δ੹຿ ભҠઌͷܾఆ τϥοΩϯά ىಈΠϕϯτͷड͚औΓ ભҠ ͜ͷ2ͭ͸෼཭͍ͨ͠

  49. ೖྗͱग़ྗͲ͏͢Δʁ

  50. ભҠઌͷܾఆ ೖྗ = ىಈ ग़ྗ = ભҠ

  51. ભҠઌͷܾఆ ೖྗ = ىಈ ग़ྗ = ભҠ

  52. ભҠઌͷܾఆ ೖྗ = ىಈ ग़ྗ = ભҠ ىಈ͕Θ͔ΔԿ͔ ભҠઌ͕Θ͔ΔԿ͔

  53. τϥοΩϯά ೖྗ = ىಈ ग़ྗ = ૹ৴ ىಈ͕Θ͔ΔԿ͔ Πϕϯτ͕Θ͔ΔԿ͔

  54. Ͳ͏΍ͬͯޮ཰Α͘ςετ͢Δʁ • ܕʹམͱ͠ࠐΉ / EnumͰύλʔϯ໢ཏ • ੹຿Λ෼͚Δ • ೖྗ x

    ग़ྗͷ૊Έ߹ΘͤΛݶఆ͢Δ ͜ΕΒ͸͢΂ͯڞ௨ͨ͠ಉ͡࿩
  55. ೖग़ྗͷ૊Έ߹ΘͤΛ੍ ݶ͢Δ

  56. ىಈܦ࿏ΛEnumͰఆٛ import UIKit import CoreSpotlight import UserNotifications enum LaunchType {

    case normal case notification(_ notification: UNNotificationRequest) case userActivity(_ userActivity: NSUserActivity) case openURL(_ url: URL) case shortcutItem(_ shortcutItem: UIApplicationShortcutItem) } ܕͰྲྀೖݩΛ͢΂ͯ໢ཏ
  57. τϥοΩϯάΠϕϯτΛEnumͰఆٛ struct LaunchTracker { enum Event: Equatable { case normal

    case localNotification(identifier: String) case remoteNotification(identifier: String) case deepLink(url: URL) case intent(word: String) case spotlight(resultIdentifier: String) case spotlight(query: String) case widget(identifier: String) case homeScreen(type: String) }
  58. τϥοΩϯάΠϕϯτΛEnumͰఆٛ struct LaunchTracker { enum Event: Equatable { case normal

    case localNotification(identifier: String) case remoteNotification(identifier: String) case deepLink(url: URL) case intent(word: String) case spotlight(resultIdentifier: String) case spotlight(query: String) case widget(identifier: String) case homeScreen(type: String) } ςετ͢Δ͜ͱ͕લఏͷ࣮૷
  59. IN OUT Normal Notification UserActivity shortcutItem URL normal localNotification remoteNotification)

    deepLink intent spotlight(result: String) spotlight(query: String) widget homeScreen
  60. launchType -> Event @discardableResult static func track(launchType: LaunchType) -> Event?

    { switch launchType { case .normal: return send(event: .normal) case .notification(let request): if request.trigger is UNPushNotificationTrigger { let event: Event = .remoteNotification(identifier: request.identifier) return send(event: event) } else if request.trigger is UNTimeIntervalNotificationTrigger { let event: Event = send(event: .localNotification(identifier: request.identifier)) return event }
  61. launchType -> Event case .userActivity(let activity): switch activity.activityType { case

    NSUserActivityTypeBrowsingWeb: guard let url = activity.webpageURL else { fatalError("unreachable") } return send(event: .deepLink(url: url)) case CSSearchableItemActionType: guard let identifier = activity.userInfo?[CSSearchableItemActivityIdentifier] as? String else { fatalError("unreachable") } return send(event: .spotlight(resultIdentifier: identifier)) case CSQueryContinuationActionType: guard let query = activity.userInfo?[CSSearchQueryString] as? String else { fatalError("unreachable") } return send(event: .spotlight(query: query)) case "com.d-date.CoordinatorExample.intent": // Siri shortcut return send(event: .intent(word: "word")) }
  62. launchType -> Event case .openURL(let url): if url.scheme == "coordinator-example-widget"

    { let identifier = url.lastPathComponent return send(event: .widget(identifier: identifier)) } else if url.scheme == "adjustSchemeExample" { //TODO: replace your adjust url scheme return send(event: .deepLink(url: url)) } else if url.scheme == "FirebaseDynamicLinksExmaple" { //TODO: handle your FDL return send(event: .deepLink(url: url)) } return nil // untracked any other urls case .shortcutItem(let item): return send(event: .homeScreen(type: item.type)) } return nil }
  63. ૹ৴ϝιουΛఆٛ @discardableResult private static func send(event: Event) -> Event {

    // TODO: send event to your analytics return event }
  64. τϥοΩϯάͷॲཧΛ෼཭Ͱ͖ͨ func start() { LaunchTracker.track(launchType: launchType) window.makeKeyAndVisible() }

  65. ͜͜·ͰͰͰ͖ͨ͜ͱ • ىಈܦ࿏͔ΒɺςετՄೳͳ΋ͷΛ੾Γग़ͨ͠ • ςετՄೳͳ΋ͷ͔Β੹຿ΛτϥοΩϯάͱભҠઌͷ ܾఆʹΘ͚ͨ • ىಈܦ࿏ͱτϥοΩϯάͷ૊Έ߹ΘͤΛ཈͑ͨ

  66. ςετ͸؆୯

  67. IN OUT Normal Notification UserActivity shortcutItem URL normal localNotification remoteNotification)

    deepLink intent spotlight(result: String) spotlight(query: String) widget homeScreen
  68. IN OUT Normal Notification UserActivity shortcutItem URL normal localNotification remoteNotification)

    deepLink intent spotlight(result: String) spotlight(query: String) widget homeScreen ͜͜Λςετ͢Δ
  69. ςετͯ͠ΈΑ͏ func testLaunchLocalPushNotification() { let identifier = "testLocalNotification" let request

    = UNNotificationRequest( identifier: identifier, content: .init(), trigger: UNTimeIntervalNotificationTrigger.init(timeInterval: 30, repeats: false) ) guard let event = LaunchTracker.track(launchType: .notification(request)) else { XCTFail() return } XCTAssertEqual(event, LaunchTracker.Event.localNotification(identifier: identifier)) }
  70. ςετͯ͠ΈΑ͏ func testLaunchLocalPushNotification() { let identifier = "testLocalNotification" let request

    = UNNotificationRequest( identifier: identifier, content: .init(), trigger: UNTimeIntervalNotificationTrigger.init(timeInterval: 30, repeats: false) ) guard let event = LaunchTracker.track(launchType: .notification(request)) else { XCTFail() return } XCTAssertEqual(event, LaunchTracker.Event.localNotification(identifier: identifier)) } μϛʔΛ࡞Δ
  71. ςετͯ͠ΈΑ͏ func testLaunchLocalPushNotification() { let identifier = "testLocalNotification" let request

    = UNNotificationRequest( identifier: identifier, content: .init(), trigger: UNTimeIntervalNotificationTrigger.init(timeInterval: 30, repeats: false) ) guard let event = LaunchTracker.track(launchType: .notification(request)) else { XCTFail() return } XCTAssertEqual(event, LaunchTracker.Event.localNotification(identifier: identifier)) } Notification͔Βىಈͨ͠ͱ͖ͷEventΛऔಘ
  72. ςετͯ͠ΈΑ͏ func testLaunchLocalPushNotification() { let identifier = "testLocalNotification" let request

    = UNNotificationRequest( identifier: identifier, content: .init(), trigger: UNTimeIntervalNotificationTrigger.init(timeInterval: 30, repeats: false) ) guard let event = LaunchTracker.track(launchType: .notification(request)) else { XCTFail() return } XCTAssertEqual(event, LaunchTracker.Event.localNotification(identifier: identifier)) } औಘͨ͠Πϕϯτͱɺਖ਼ղΛൺֱ
  73. ςετͯ͠ΈΑ͏ func testLaunchDeepLink() { let userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) let

    url = URL(string: "https://example.coordinator.com/123456")! userActivity.webpageURL = url guard let event = LaunchTracker.track(launchType: .userActivity(userActivity)) else { XCTFail() return } XCTAssertEqual(event, .deepLink(url: url)) }
  74. ςετͯ͠ΈΑ͏ func testLaunchSpotlightClickResult() { let userActivity = NSUserActivity(activityType: CSSearchableItemActionType) userActivity.userInfo

    = [CSSearchableItemActivityIdentifier: "123456"] guard let event = LaunchTracker.track(launchType: .userActivity(userActivity)) else { XCTFail() return } XCTAssertEqual(event, .spotlight(resultIdentifier: "123456")) }
  75. ςετͯ͠ΈΑ͏ func testLaunchRemotePushNotification() { let identifier = "testLocalNotification" let request

    = UNNotificationRequest( identifier: identifier, content: .init(), trigger: UNPushNotificationTrigger.init() ) guard let event = LaunchTracker.track(launchType: .notification(request)) else { XCTFail() return } XCTAssertEqual(event, LaunchTracker.Event.localNotification(identifier: identifier)) }
  76. ςετͯ͠ΈΑ͏ func testLaunchRemotePushNotification() { let identifier = "testLocalNotification" let request

    = UNNotificationRequest( identifier: identifier, content: .init(), trigger: UNPushNotificationTrigger.init() ) guard let event = LaunchTracker.track(launchType: .notification(request)) else { XCTFail() return } XCTAssertEqual(event, LaunchTracker.Event.localNotification(identifier: identifier)) }
  77. Initializer͕ެ։͞Ε͍ͯͳ͍ @available(iOS 10.0, *) open class UNNotificationTrigger : NSObject, NSCopying,

    NSSecureCoding { open var repeats: Bool { get } } @available(iOS 10.0, *) open class UNPushNotificationTrigger : UNNotificationTrigger { }
  78. Q. Ͳ͏͢Δʁ

  79. Q. Ͳ͏͢Δʁ A. ఘΊΔʂ

  80. ఘΊΔʁ • ؒ઀తʹςετͰ͖ͳ͍͔Λߟ͑Δ • ϩʔΧϧϓογϡ͡Όͳ͍ˠϦϞʔτϓογϡ • ແཧ͠ͳ͍ʢϢχοτςετ͸͋͘·Ͱखஈʣ

  81. ࠓճ࿩ͨ͜͠ͱ • ىಈܦ࿏Λ୊ࡐʹͨ͠Ϣχοτςετͷ࿩ • ςετͰ͖ͳ͍΋ͷΛςετՄೳʹͳΔΑ͏ʹ෼ׂ͢Δ • ೖྗ x ग़ྗͷ૊Έ߹Θ͕ͤগͳ͘ͳΔΑ͏ʹɺ੍໿Λ ͚ͭΔ

    • ͦΕͰ΋μϝͳΒແཧ͸͠ͳ͍
  82. ࠓճ࿩ͤͳ͔ͬͨ͜ͱ • ઃܭύλʔϯͷ࿩ʢCoordinator, RouterͳͲ) • ձࣾͷϓϩμΫτͰͲ͏͍ͯ͠Δ͔

  83. ӶҙࣥචதͰ͢

  84. import UIKit import CoreSpotlight import UserNotifications @UIApplicationMain class AppDelegate: UIResponder,

    UIApplicationDelegate { var window: UIWindow? private var appCoordinator: AppCoordinator? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds) self.appCoordinator = AppCoordinator(window: window!, launchType: .normal) appCoordinator!.start() return true } func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds) let type: LaunchType = .userActivity(userActivity) self.appCoordinator = AppCoordinator(window: window!, launchType: type) appCoordinator!.start() return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { self.window = UIWindow(frame: UIScreen.main.bounds) let type: LaunchType = .openURL(url) self.appCoordinator = AppCoordinator(window: window!, launchType: type) appCoordinator!.start() return true } func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { self.window = UIWindow(frame: UIScreen.main.bounds) let type: LaunchType = .shortcutItem(shortcutItem) self.appCoordinator = AppCoordinator(window: window!, launchType: type) appCoordinator!.start() completionHandler(true) } } extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { self.window = UIWindow(frame: UIScreen.main.bounds) let request = response.notification.request let type: LaunchType = .notification(request) self.appCoordinator = AppCoordinator(window: window!, launchType: type) appCoordinator!.start() completionHandler() } } ※ιʔείʔυ͸ΠϝʔδͰ͢
  85. ͜ͷ͙Β͍εοΩϦ͠·͢ ※͋͘·Ͱىಈॲཧͷ࿩Ͱ͢

  86. ձࣾͷࣄྫ

  87. ձࣾͷࣄྫ ͪ͜Β·Ͱ

  88. 9/12ɾ13 @DeNA

  89. Thank you!