$30 off During Our Annual Pro Sale. View Details »

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

d_date
August 31, 2018

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

2018/08/31 iOSDC Japan 2018

d_date

August 31, 2018
Tweet

More Decks by d_date

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. ໔੹ࣄ߲

    View Slide

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

    View Slide

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

    View Slide

  9. A. 6Ҏ্͸͋Δ

    View Slide

  10. 1. ௨ৗىಈ

    View Slide

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

    View Slide

  12. 2. ϓογϡ௨஌

    View Slide

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

    View Slide

  14. ϓογϡ௨஌
    • Local Push Notification
    • Remote Push Notification
    ~ iOS 9: AppDelegate
    iOS 10 ~: UserNotifiaction.framework

    View Slide

  15. 3. Universal Links

    View Slide

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

    View Slide

  17. Universal Links (Deep Link)
    • ΞϓϦະΠϯετʔϧͷ৔߹
    • Web → App Store → App
    • didFinishLaunching͕ݺ͹ΕΔʢ௨ৗىಈʣ
    • URLΛ։͍ͯ೚ҙͷϖʔδ΁ʢopenURL)

    View Slide

  18. Universal Links (Deep Link)
    • ΞϓϦΠϯετʔϧࡁΈͷ৔߹
    • ΞϓϦະΠϯετʔϧͷ৔߹
    Ϣʔβʔ͔ΒΈΔͱ1ͭͳͷʹɺ࣮૷͸2ύλʔϯ

    View Slide

  19. 4. Spotlight

    View Slide

  20. Spotlight
    • ݕࡧ͔Βͷىಈ
    • ݕࡧ݁Ռ
    (CSSearchableItemActionType)
    • ΞϓϦ಺ݕࡧʢiOS 10 ~ʣ
    (CSQueryContinuationActionType)
    • ͲͪΒ΋User Activityѻ͍ʹͳΔ

    View Slide

  21. 5. 3D Touch

    View Slide

  22. 3D Touch ( Home Screen Quick Action )
    • AppDelegateͷperformShortcutItem
    • ఆٛͨ͠ϝχϡʔͷ਺͚ͩભҠઌ͕͋Δ

    View Slide

  23. 6.Widget

    View Slide

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

    View Slide

  25. ΞϓϦͷىಈܦ࿏
    • ௨ৗىಈ
    • ϓογϡ௨஌ʢLocal / Remoteʣ
    • σΟʔϓϦϯΫ
    • Core Spotlight (Result Action / Query )
    • Home Screen Quick Action
    • Widget

    View Slide

  26. 7. Siri Shortcut

    View Slide

  27. NDA

    View Slide

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

    View Slide

  29. ΞϓϦͷىಈܦ࿏
    • ௨ৗىಈ
    • ϓογϡ௨஌ʢLocal / Remoteʣ
    • σΟʔϓϦϯΫ
    • Core Spotlight (Result Action / Query )
    • Home Screen Quick Action
    • Siri Shortcut
    • etc…

    View Slide

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

    View Slide

  31. ىಈܦ࿏͸͜ͷ̐ͭͷϝιου ( AppDelegate )
    • application:didFinishLaunching
    • application:openURL
    • application:continueUserActivity
    • application:performAction

    View Slide

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

    View Slide

  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()
    }
    }

    ※ιʔείʔυ͸ΠϝʔδͰ͢

    View Slide

  34. έʔεελσΟ
    • ىಈॲཧ͕ͻͱͭ૿͑ͨͷͰɺ࣮૷͠·ͨ͠ɻɹɹɹɹɹ
    खಈͰಈ࡞֬ೝͯ͠΋໰୊͋Γ·ͤΜͰͨ͠
    • ςετʁॻ͍ͯ·ͤΜ͜͜͠͸ॻ͚ͳ͍Ͱ͢Ͷ
    • ະϩάΠϯ࣌ʹUniversal Link͔Β։͍ͨ࣌͸ɺͬͪ͜
    ͷը໘ʹඈ͹͞ͳ͍ͱɻAppDelegateʹॲཧೖΕ͓ͯ
    ͖·͢Ͷ

    View Slide

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

    View Slide

  36. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. ςετͰ͖Δઃܭ΁

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  54. Ͳ͏΍ͬͯޮ཰Α͘ςετ͢Δʁ
    • ܕʹམͱ͠ࠐΉ / EnumͰύλʔϯ໢ཏ
    • ੹຿Λ෼͚Δ
    • ೖྗ x ग़ྗͷ૊Έ߹ΘͤΛݶఆ͢Δ
    ͜ΕΒ͸͢΂ͯڞ௨ͨ͠ಉ͡࿩

    View Slide

  55. ೖग़ྗͷ૊Έ߹ΘͤΛ੍
    ݶ͢Δ

    View Slide

  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)
    }
    ܕͰྲྀೖݩΛ͢΂ͯ໢ཏ

    View Slide

  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)
    }

    View Slide

  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)
    }
    ςετ͢Δ͜ͱ͕લఏͷ࣮૷

    View Slide

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

    View Slide

  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
    }

    View Slide

  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"))
    }

    View Slide

  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
    }

    View Slide

  63. ૹ৴ϝιουΛఆٛ
    @discardableResult
    private static func send(event: Event) -> Event {
    // TODO: send event to your analytics
    return event
    }

    View Slide

  64. τϥοΩϯάͷॲཧΛ෼཭Ͱ͖ͨ
    func start() {
    LaunchTracker.track(launchType: launchType)
    window.makeKeyAndVisible()
    }

    View Slide

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

    View Slide

  66. ςετ͸؆୯

    View Slide

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

    View Slide

  68. IN OUT
    Normal
    Notification
    UserActivity
    shortcutItem
    URL
    normal
    localNotification
    remoteNotification)
    deepLink
    intent
    spotlight(result: String)
    spotlight(query: String)
    widget
    homeScreen
    ͜͜Λςετ͢Δ

    View Slide

  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))
    }

    View Slide

  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))
    }
    μϛʔΛ࡞Δ

    View Slide

  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Λऔಘ

    View Slide

  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))
    }
    औಘͨ͠Πϕϯτͱɺਖ਼ղΛൺֱ

    View Slide

  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))
    }

    View Slide

  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"))
    }

    View Slide

  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))
    }

    View Slide

  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))
    }

    View Slide

  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 {
    }

    View Slide

  78. Q. Ͳ͏͢Δʁ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  83. ӶҙࣥචதͰ͢

    View Slide

  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()
    }
    }
    ※ιʔείʔυ͸ΠϝʔδͰ͢

    View Slide

  85. ͜ͷ͙Β͍εοΩϦ͠·͢
    ※͋͘·Ͱىಈॲཧͷ࿩Ͱ͢

    View Slide

  86. ձࣾͷࣄྫ

    View Slide

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

    View Slide

  88. 9/12ɾ13 @DeNA

    View Slide

  89. Thank you!

    View Slide