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

Introduction to micro interactions for iOS apps

kiwi
September 02, 2018

Introduction to micro interactions for iOS apps

iOSDC 2018 (day3)
「iOSマイクロインタラクション入門」登壇資料
https://fortee.jp/iosdc-japan-2018/proposal/f21bf617-3e9c-4ec0-833c-bc8dbb6965e5

(プロポーザルより)
マイクロインタラクションとは、ユーザーが操作した際に表示される、ちょっとしたアニメーションやフィードバックなどのアクションのことです。
Facebookなどのアプリで積極的に採用されており、自分のアプリにも入れてみたい、という方に向けて、マイクロインタラクションの使いどころや実装例を紹介します。

kiwi

September 02, 2018
Tweet

More Decks by kiwi

Other Decks in Technology

Transcript

  1. J04ϚΠΫϩΠϯλϥΫγϣϯ
    ೖ໳
    LJXJ!LPHB@XJXJ
    LJXJ
    /*'5:-JGFTUZMF$P MUE
    J04%$
    ެ։൛

    View Slide

  2. ެ։൛ʹؔ͢Δ͝஫ҙ
    • εϥΠυதʹҾ༻ͨ͠ଞࣾΞϓϦͷྫʹ͍ͭͯ͸ɺ
    ͝໎࿭͕͔͔Δ͜ͱΛආ͚ΔͨΊɺμϛʔը૾ʹஔ͖׵͓͑ͯΓ·͢ɻ
    • ετΞͷ%-ϦϯΫΛ23ίʔυʹͨ͠΋ͷΛܝࡌ͠·ͨ͠ͷͰɺ
    ͥͻࣗ͝਎ͷ୺຤Ͱ͝ཡ͍ͩ͘͞ɻ
    • ঺հͨ͠ΞϓϦͷಈ͖͸ɺ ࣌఺Ͱͷ΋ͷͰ͢ɻ
    ࠓޙΞϓϦͷಈ࡞͸มΘΔ͜ͱ͕͋Γ·͢ͷͰྃ͝ঝ͍ͩ͘͞ɻ

    View Slide

  3. ࣗݾ঺հ
    kiwi (キウイ)
    koga_wiwi
    kiwi-26

    View Slide

  4. View Slide

  5. χϑςΟෆಈ࢈ΞϓϦͱϚΠΫϩΠϯλϥΫγϣϯ
    ϚΠΫϩ
    ΠϯλϥΫγϣϯ

    View Slide

  6. J04ϚΠΫϩΠϯλϥΫγϣϯ
    ೖ໳
    LJXJ!LPHB@XJXJ
    LJXJ
    /*'5:-JGFTUZMF$P MUE
    J04%$

    View Slide

  7. ϚΠΫϩΠϯλϥΫγϣϯ

    View Slide

  8. ϚΠΫϩΠϯλϥΫγϣϯ
    ૬ޓ࡞༻

    View Slide

  9. ϚΠΫϩΠϯλϥΫγϣϯ
    ૬ޓ࡞༻
    ਓ͕ؒԿ͔ΞΫγϣϯΛͨ࣌͠ɺ
    ૬खଆͷγεςϜ͕ͦͷΞΫγϣϯʹ
    ରԠͨ͠ϦΞΫγϣϯΛ͢Δ͜ͱ
    IUUQTGFSSFUQMVTDPN ΑΓҰ෦վม

    View Slide

  10. ϚΠΫϩΠϯλϥΫγϣϯ
    Ϣʔβʔ͕ΞΫγϣϯͨ͠ࡍʹड͚औΔ
    γεςϜଆ͔Βͷͪΐͬͱͨ͠ϦΞΫγϣϯ

    View Slide

  11. ඪ४ͷϚΠΫϩΠϯλϥΫγϣϯ
    ηϧΛλοϓ͢Δͱ
    ͙͢ʹഎܠ৭͕มΘΔ
    ࣍ͷ֊૚ͷը໘͕
    ӈଆ͔ΒεϥΠυ͢Δ
    λΠτϧ͕ঃʑʹখ͘͞ͳΓ
    ͦͷ··໭ΔϘλϯʹͳΔ
    ઃఆΞϓϦͷτοϓͰ
    6*5BCMF7JFXͷ߲໨ηϧΛ
    λοϓͨ͠ͱ͖ͷಈ͖

    View Slide

  12. ϚΠΫϩΠϯλϥΫγϣϯͷ໾ׂ
    ૢ࡞ͷ৺஍Α͞ɾշײ

    View Slide

  13. ࢖͍উख ͕Α͘ͳΔ
    ࢖͍৺஍ ͕Α͘ͳΔ

    View Slide

  14. ͞·͟·ͳϚΠΫϩΠϯλϥΫγϣϯ
    ର৅ͷ7JFXͷݟͨ໨ʢ৭ͳͲʣͷมԽ
    ૢ࡞ͨ͠ͱ͖ͷޮՌԻʢྫ5XJUUFSެࣜʣ
    ૢ࡞Λͨ͠ͱ͖ͷ୺຤ͷৼಈ %BZ !%
    Ξχϝʔγϣϯ

    View Slide

  15. ΞχϝʔγϣϯΛ࢖ͬͨϚΠΫϩΠϯλϥΫγϣϯ
    NJOOF

    View Slide

  16. 6TFBOJNBUJPOBOENPUJPOFGGFDUTKVEJDJPVTMZ
    Ξχϝʔγϣϯ͸ݡ͘࢖͏ɻ
    %PO`UVTFBOJNBUJPOGPSUIFTBLFPGVTJOHBOJNBUJPO
    lΞχϝʔγϣϯΛ࢖͏ͨΊzʹΞχϝʔγϣϯΛ࢖Θͳ͍ɻ
    ӳจ)VNBO*OUFSGBDF(VJEFMJOF "OJNBUJPO
    ΑΓ

    View Slide

  17. ΞχϝʔγϣϯΛ6*Ͱ࢖͏ͱͰ͖Δ͜ͱ
    Ϧονͳදݱ͕
    Ͱ͖Δ
    ଟ͘ͷ৘ใΛ
    ఻͑Δ͜ͱ͕Ͱ͖Δ
    ஫ҙΛҾ͘
    ʮΠϯλʔϑΣΠεͱΞχϝʔγϣϯʯ
    IUUQTXXXZPVUVCFDPNXBUDI WO0U$*H@P ΑΓ

    View Slide

  18. Ϧονͳදݱ͕Ͱ͖Δ
    ૂ͑ΔޮՌ
    • ϫΫϫΫײ
    • ੈք؍ͷදݱ
    ࢖͍Ͳ͜Ζ
    • ήʔϜͳͲੈք؍͕ڧ͍ΞϓϦ
    • ΢ΥʔΫεϧʔը໘
    5PLZP %JTOFZ3FTPSU"QQ
    ΢ΥʔΫεϧʔը໘
    ىಈ࣌ͷζʔϜͷಈ͖

    View Slide

  19. ଟ͘ͷ৘ใΛ఻͑Δ͜ͱ͕Ͱ͖Δ
    ૂ͑ΔޮՌ
    • ཧղ͠΍͢͞ͷ޲্
    ࢖͍Ͳ͜Ζ
    • λοϓ࣌ͷΦϒδΣΫτͷมԽ
    ૢ࡞ͷ੒ޭΛϑΟʔυόοΫ

    • ը໘ભҠ
    • άϥϑͷ࣌ܥྻදࣔ
    /JLF3VO$MVC
    ϥϯͷৄࡉը໘Ͱͷ
    ૸ߦϧʔτදࣔͷಈ͖

    View Slide

  20. ஫ҙΛҾ͘
    ૂ͑ΔޮՌ
    • ը໘ͷҰ෦Λಈ͔ͯ͠஫໨ͤ͞Δ
    ࢖͍Ͳ͜Ζ
    • Χʔτ௥ՃͷΞχϝʔγϣϯ
    • ͦͷ΄͔ը໘ͷҰ෦ʹ஫໨͍ͤͨ͞ͱ͖
    NJOOF
    ঎඼ΛΧʔτʹ
    ೖΕͨͱ͖ͷಈ͖

    View Slide

  21. ΞχϝʔγϣϯΛ6*Ͱ࢖͏ͱͰ͖Δ͜ͱ
    Ϧονͳදݱ͕
    Ͱ͖Δ
    ଟ͘ͷ৘ใΛ
    ఻͑Δ͜ͱ͕Ͱ͖Δ
    ஫ҙΛҾ͘
    ʮΠϯλʔϑΣΠεͱΞχϝʔγϣϯʯ
    IUUQTXXXZPVUVCFDPNXBUDI WO0U$*H@P ΑΓ

    View Slide

  22. ࡞ͬͯΈΑ͏

    View Slide

  23. ͓୊
    ԡ͢ͱbΆΑΜ`ͱ͢ΔϘλϯ

    View Slide

  24. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    Ϙλϯλοϓʹର͢ΔϑΟʔυόοΫͷछ
    *OTUBHSBN ΄͔ଟ͘ͷΞϓϦͰಋೖ
    ͳʹ΋ͳ͍ͱ͜Ζ͔Β·ͣೖΕͯΈ͍ͨ
    ͕͋͜ΕΔΞχϝʔγϣϯ/P ཁग़య

    *OTUBHSBN
    ͍͍ͶϘλϯͷಈ͖

    View Slide

  25. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    ͲΜͳಈ͖Λ͍ͯ͠Δͷ͔
    ίϚૹΓͯ͠ΈΔ
    ࣸਅΞϓϦΛ࢖͍ɺ
    ಈըͷαϜωΠϧΛΏͬ͘ΓεϥΠυͯ͠
    Ξχϝʔγϣϯͷಈ͖Λ֬ೝ͍ͯ͠Δ༷ࢠ

    View Slide

  26. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    Α͘ݟΔͱɺখ͘͞ͳ͔ͬͯΒɺେ͖͘ͳͬͯΔ
    *OTUBHSBN

    View Slide

  27. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    ΆΑΜͱ͢Δʢόωͷಈ͖ͷʣΞχϝʔγϣϯͷઐ໳Ո
    UIView.animate(withDuration:delay:usingSpringWithDamping:initial
    SpringVelocity:options:animations:completion:)

    View Slide

  28. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    • ඵ਺ͱΞχϝʔγϣϯऴྃޙͷঢ়ଶʢΫϩʔδϟʣΛࢦఆ͢Δͱɺ
    ͦͷؒΛࣗಈͰิؒͯ͠Ξχϝʔγϣϯͯ͘͠ΕΔ
    • ࢦఆͰ͖Δ಺༰͸6*7JFXͷϓϩύςΟ
    • GSBNF΍CPVOETɺഎܠ৭ɺಁ໌౓
    • USBOTGPSN ճస ֦େॖখ
    %BZ-5!5SBDL#
    • ஗Εඵ਺΍ΠʔζΠϯΞ΢τɺऴྃޙͷॲཧͷࢦఆ͕Մೳͳ೿ੜܕ΋
    • ऴྃॲཧͰผͷΞχϝʔγϣϯΛೖΕΔ͜ͱͰɺ࿈ଓͨ͠Ξχϝʔ
    γϣϯ͕Մೳ
    UIView.animate(withDuration:animations:)

    View Slide

  29. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    UIView.animate(withDuration:animations:)
    imageView.bounds.size = CGSize(width: 36, height: 36)
    UIView.animate(
    withDuration: 0.3, // 秒数
    delay: 0, // 0秒後に開始
    options: .curveEaseIn, // オプション(カーブ, リピート等)
    animations: {
    self.imageView.bounds.size = CGSize(width: 44, height: 44)
    },
    completion: nil)

    View Slide

  30. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    ΆΑΜͱ͢Δʢόωͷಈ͖ͷʣΞχϝʔγϣϯͷઐ໳Ո
    UIView.animate(withDuration:delay:usingSpringWithDamping:initial
    SpringVelocity:options:animations:completion:)

    View Slide

  31. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    imageView.bounds.size = CGSize(width: 36*0.7, height: 36*0.7)
    UIView.animate(
    withDuration: 0.4,
    delay: 0.0,
    usingSpringWithDamping: 0.5,
    initialSpringVelocity: 0.0,
    options: .curveEaseInOut,
    animations: {
    self.imageView.bounds.size = CGSize(width: 36, height: 36)
    },
    completion: nil)

    View Slide

  32. ΞχϝʔγϣϯΛ࡞ͬͯΈΔr ΆΑΜͱ͢ΔϘλϯ
    imageView.bounds.size = CGSize(width: 36*0.7, height: 36*0.7)
    UIView.animate(
    withDuration: 0.4,
    delay: 0.0,
    usingSpringWithDamping: 0.5, // バネの振幅
    initialSpringVelocity: 0.0, // バネの初速
    options: .curveEaseInOut,
    animations: {
    self.imageView.bounds.size = CGSize(width: 36, height: 36)
    },
    completion: nil)

    View Slide

  33. σϞ
    ʢ͜͜ʹσϞΛೖΕΔʣ

    View Slide

  34. ͓୊
    ը૾͕Ҡಈ͠ͳ͕Βը໘ભҠ

    View Slide

  35. ΞχϝʔγϣϯΛ࡞ͬͯΈΔ ը໘ભҠ
    ࣍ͷը໘ͱڞ௨ͷཁૉΛɺ࣍ͷը໘ʹ߹Θͤͯ
    Ґஔͷมߋɾ֦େॖখΛߦ͍ͳ͕Βܧଓදࣔ
    ը໘ؒͷؔ܎ΛΑΓڧ͘ఏࣔ͢Δ

    View Slide

  36. ΞχϝʔγϣϯΛ࡞ͬͯΈΔ ը໘ભҠ
    ࣍ͷը໘ͱڞ௨ͷཁૉΛɺ࣍ͷը໘ʹ߹Θͤͯ
    Ґஔͷมߋɾ֦େॖখΛߦ͍ͳ͕Βܧଓදࣔ
    ը໘ؒͷؔ܎ΛΑΓڧ͘ఏࣔ͢Δ
    1JOUFSFTU΍NJOOFͳͲͰ࢖༻
    ϚςϦΞϧσβΠϯͰ΋༗໊
    NJOOF
    ঎඼ͷҰཡ͔Β
    ৄࡉʹભҠ͢Δಈ͖

    View Slide

  37. ΞχϝʔγϣϯΛ࡞ͬͯΈΔ ը໘ભҠ
    • ը໘ؒͰͲΜͳભҠΛ͢Δ͔Λදͨ͢Ίͷϓϩτίϧ
    • VTJOHͰ౉͞ΕΔ 6*7JFX$POUSPMMFS$POUFYU5SBOTJUJPOJOHܕͷҾ਺
    ͔Βɺલޙͷ7JFX$POUSPMMFS΍7JFX͕औಘՄೳ
    UIViewControllerAnimatedTransitioning プロトコル
    animateTransition(using:) メソッド
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView // アニメーション中のcontainer
    let fromView = transitionContext.view(forKey: .from)! // 今まで表示していたview
    let toView = transitionContext.view(forKey: .to)! // これから表示するview
    ...

    View Slide

  38. ΞχϝʔγϣϯΛ࡞ͬͯΈΔ ը໘ભҠ
    • USBOTJUJPO$POUFYUDPOUBJOFS7JFX͕Ξχϝʔγϣϯͷؒදࣔ͞ΕΔ
    • ભҠޙͷ7JFX΍Ξχϝʔγϣϯதʹදࣔ͢Δ7JFXΛDPOUBJOFS7JFXʹ
    BEE4VCWJFX͠ɺΞχϝʔγϣϯͤ͞Δ
    • ࠓճ͸ɺભҠઐ༻ͷ*NBHF7JFXΛDPOUBJOFSʹ௥Ճ͠ɺ
    ϦετͷҐஔ͔Βৄࡉը໘ͰͷҐஔ·Ͱಈ͔͢ͱ͍͏ํ਑
    • ৄࡉΛεέϧτϯදࣔ͠ɺͦͷதͰॳظҐஔ͔Βಈ͔͢ํ๏΋͋Δ
    • Ξχϝʔγϣϯ͕ऴΘͬͨΒ DPOUFYUDPNQMFUF5SBOTJUJPO USVF

    UIViewControllerAnimatedTransitioning プロトコル
    animateTransition(using:) メソッド

    View Slide

  39. ࣮૷ྫ
    func animateTransition(using transitionContext:
    UIViewControllerContextTransitioning) {
    presenting = operation == UINavigationControllerOperation.push
    // containerView, fromView, toView を取得
    let containerView = transitionContext.containerView
    let fromView = transitionContext.view(forKey: .from)!
    let toView = transitionContext.view(forKey: .to)!
    // 詳細画面側のViewを取得し、アニメーション中は非表示にする
    let detailView = presenting ? toView : fromView
    toView.alpha = 0
    // 詳細画面のImageViewの位置, 遷移用ImageViewの初期位置と最終位置
    let detailFrame = CGRect(x: 0, y: 64, width: detailView.frame.width,
    height: detailView.frame.width)

    View Slide

  40. ࣮૷ྫ
    // 詳細画面のImageViewの位置, 遷移用ImageViewの初期位置と最終位置
    let detailFrame = CGRect(x: 0, y: 64, width: detailView.frame.width,
    height: detailView.frame.width)
    let initialFrame = presenting ? originFrame : detailFrame
    let finalFrame = presenting ? detailFrame : originFrame
    // 遷移中のみ表示させるImageView
    let transitionImageView = UIImageView(frame: initialFrame)
    transitionImageView.image = listImageView.image
    transitionImageView.contentMode = .scaleAspectFill
    transitionImageView.clipsToBounds = true
    containerView.addSubview(toView)
    containerView.addSubview(transitionImageView)
    containerView.bringSubview(toFront: transitionImageView)

    View Slide

  41. ࣮૷ྫ
    // 最初の0.2秒で遷移元の画面を非表示にする
    // containerViewに追加しているImageViewだけが表示される
    UIView.animate(withDuration: duration/5, animations: {
    fromView.alpha = 0
    })
    // 0.2秒後からEaseInOutでImageViewの位置を移動
    // 合わせて、一覧に戻る遷移の場合は一覧画面を徐々に表示
    UIView.animate(withDuration: duration*4/5, delay: duration/5, options:
    [.curveEaseInOut], animations: {
    transitionImageView.frame = finalFrame
    if !self.presenting {
    toView.alpha = 1
    }
    }, completion: { _ in
    // 非表示にした詳細画面を表示

    View Slide

  42. ࣮૷ྫ
    if !self.presenting {
    toView.alpha = 1
    }
    }, completion: { _ in
    // 非表示にした詳細画面を表示
    // 遷移中に表示していたimageViewを削除
    if self.presenting {
    detailView.alpha = 1
    } else {
    self.dismissCompletion?()
    }
    transitionImageView.removeFromSuperview()
    transitionContext.completeTransition(true)
    })
    }

    View Slide

  43. σϞ
    ʢ͜͜ʹσϞΛೖΕΔʣ

    View Slide

  44. ͓·͚6*7JFX1SPQFSUZ"OJNBUPS ʹ͍ͭͯ
    $PSF"OJNBUJPO
    $"-BZFSͰͷΞχϝʔγϣϯ

    6*7JFX
    $"-BZFSΛ಺෦ʹ͍࣋ͬͯΔ

    6*7JFX1SPQFSUZ"OJNBUPS
    J04ʙ ͍Ζ͍ΖઃఆͰ͖Δ΍ͭ

    ಺෦ͰݺΜͰΔ ಺෦ͰݺΜͰΔʜʜͱࢥ͏
    • ΞχϝʔγϣϯͷҰ࣌ఀࢭ΍ٯ࠶ੜ
    • ΠʔδϯάͷΧʔϒΛࣗ෼Ͱઃఆ
    • ಈతʹΞχϝʔγϣϯΛ௥Ճ
    %BZ !%

    View Slide

  45. ͍͞͝ʹ
    • ϚΠΫϩΠϯλϥΫγϣϯΛద੾ʹऔΓೖΕΔ͜ͱͰɺ
    ΞϓϦͷ࢖͍৺஍͕Α͘ͳΓɺϢʔβʔମݧ͕޲্͢Δ
    • ؤுͬͯεςοϓΞοϓ͍͖ͯ͠·͠ΐ͏ʂ
    ՝୊Λ
    ղܾͰ͖Δ
    εϜʔζʹ
    ՝୊ղܾ
    ࢖͍উख

    ՝୊ղܾ͕
    շײʹͳΔ
    ࢖͍৺஍

    ҆ఆͨ͠ಈ࡞
    ը໘ͷߏ੒ཁૉ΍
    ભҠͷ߹ཧԽ
    ಈ࡞ͷΧΫ͖ͭ
    ϚΠΫϩΠϯλϥΫγϣϯ

    View Slide

  46. ࢀߟࢿྉ
    • 6*7JFX "OJNBUJPO$PSF"OJNBUJPO
    • IUUQTEFWFMPQFSBQQMFDPNKQEPDVNFOUBUJPO7JFX1(@J1IPOF04QEG
    • IUUQTEFWFMPQFSBQQMFDPNKQEPDVNFOUBUJPO$PSF"OJNBUJPO@HVJEFQEG
    • IUUQTTUPSFSBZXFOEFSMJDIDPNQSPEVDUTJPTBOJNBUJPOTCZUVUPSJBMT
    • IUUQTRJJUBDPNIBDIJOPCVJUFNTEDDCB
    • 6*7JFX1SPQFSUZ"OJNBUPS
    • IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOVJLJUBOJNBUJPO@BOE@IBQUJDT
    QSPQFSUZ@CBTFE@BOJNBUJPOT
    • IUUQTTQFBLFSEFDLDPNIFEKJSPHJOUSPEVDUJPOUP
    VJWJFXQSPQFSUZBOJNBUPS
    • IUUQTTQFBLFSEFDLDPNIFEKJSPHVJWJFXQSPQFSUZBOJNBUPSEFTIJYJBO
    TVSVSJUVUJOBBOJNFTJZPOCJBPYJBO

    View Slide