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

iOSでグラフを描くために必要な知識

Masashi-Sutou
September 02, 2018

 iOSでグラフを描くために必要な知識

iOSDC Japan 2018/9/2 15:10~ Track A レギュラートーク(15分)
Sample: https://github.com/masashi-sutou/PieGraphSampler

Masashi-Sutou

September 02, 2018
Tweet

More Decks by Masashi-Sutou

Other Decks in Programming

Transcript

  1. J04ͰάϥϑΛඳͨ͘Ίʹ
    ඞཁͳ஌ࣝ
    ʙ5SBDL"ϨΪϡϥʔτʔΫʢ෼ʣ

    View Slide

  2. ελσΟϓϥεגࣜձࣾͰJ04ΤϯδχΞΛͯ͠·͢
    ਢ౻ক࢙ʢ!LVSPUZBOOʣ

    View Slide

  3. 4UVEZQMVTͷάϥϑʢҰ෦ʣ

    View Slide

  4. ຊ೔ͷτʔΫͰ࿩͢͜ͱ
    ෼ͱ͍͏୹͍࣌ؒͰ͕͢
    άϥϑΛඳͨ͘Ίʹඞཁͳ஌ࣝΛ
    ͋ΔҰͭͷάϥϑͰՄೳͳݶΓઆ໌͠·͢

    View Slide

  5. ͦΕ͸ԁάϥϑͰ͢

    View Slide

  6. άϥϑͷલʹԁʢ୯Ґԁʣͷ͓͞Β͍
    w ୈʙ̐৅ݶͷ஌͕ࣝඞཁ
    w ๮ંΕઢάϥϑ͸ୈ̍৅ݶͰ͋
    Δ͜ͱ͕ଟ͘ɺඳը͸ͦΕ΄Ͳ
    ೉͘͠ͳ͍
    w TJOВ DPTВ UBOВʢࡾ֯ؔ਺ʣɺ
    ϥδΞϯʢݽ౓๏ʣ
    w $PSF(SBQIJDT $PSF"OJNBUJPO
    y
    x





    ୈ৅ݶ
    ୈ৅ݶ
    ୈ৅ݶ ୈ৅ݶ
    SBE

    View Slide

  7. ԁάϥϑΛࣗ༝ʹඳ͘͜ͱ͕ग़དྷΕ͹
    ଞͷछྨͷάϥϑ͸ԁάϥϑͷ஌ࣝΛ
    Ԡ༻ͯ͠΄΅ඳ͚ͦ͏ʂ
    ͱ͍͏͜ͱ͸ɾɾɾ

    View Slide

  8. w 6*,JUͱ$PSF"OJNBUJPO͸ࠨ্۱ݪ఺ʢ6QQFS-FGU0SJHJOʣ
    Ͱɺ$PSF(SBQIJDT͸ࠨԼ۱ݪ఺ʢ-PXFS-FGU0SJHJOʣ
    w 6*#F[JFS1BUI͸ɺ$PSF(SBQIJDTͷύεؔ࿈ػೳΛϥοϓ͠
    ͨ0CKFDUJWF$ͷϥούʔͰ6*,JUͷΫϥε
    w $"-BZFSʹ6*#F[JFS1BUIΛ$(1BUIͰ౉ͤ͹ɺΞχϝʔγϣ
    ϯͷௐ੔͕$"#BTJD"OJNBUJPOͰՄೳ
    J04ͷάϥϑඳըͷجຊ΋͓͞Β͍

    View Slide

  9. άϥϑͰٻΊΒΕͦ͏ͳཁ݅Λͭఆٛ
    άϥϑͷඳը
    σʔλϥϕϧͷඳը
    Ξχϝʔγϣϯ
    δΣενϟʔ

    View Slide

  10. αϯϓϧ1JF(SBQI4BNQMFS
    IUUQTHJUIVCDPNNBTBTIJTVUPV1JF(SBQI4BNQMFS

    View Slide

  11. άϥϑͷඳը

    View Slide

  12. "OHMFͳͷͰ֯౓ͩͳͱࢥ͏͚Ͳ
    // endAngle ͸ π / 2 = 90౓
    let endAngle: CGFloat = .pi / 2
    let path = UIBezierPath(arcCenter: .zero,
    radius: 100,
    startAngle: 0,
    endAngle: endAngle,
    clockwise: true)

    View Slide

  13. ֯౓Ͱ͸ͳ͘ހ౓ʢϥδΞϯʣ
    // endAngle ͸ π / 2 = 90౓
    let endAngle: CGFloat = .pi / 2
    let path = UIBezierPath(arcCenter: .zero,
    radius: 100,
    startAngle: 0,
    endAngle: endAngle,
    clockwise: true)

    View Slide

  14. Ͱ΋ɺΠϝʔδͷ͠΍͍֯͢౓Ͱॻ͖͍ͨ
    // ֯౓ʢ90౓ʣΛހ౓ʢπ / 2ʣʹม׵͢Δ
    let endAngle = CGFloat(90.0.radianValue)
    let path = UIBezierPath(arcCenter: .zero,
    radius: 100,
    startAngle: 0,
    endAngle: endAngle,
    clockwise: true)

    View Slide

  15. J04͔Β͸.FBTVSFNFOUͰม׵Մೳ
    extension Double {
    // ֯౓(degree) -> ހ౓(radian)
    var radianValue: Double {
    if #available(iOS 10.0, *) {
    let dm = Measurement(value: self, unit: UnitAngle.degrees)
    let rm = dm.converted(to: .radians)
    return rm.value
    } else {
    return self / 180 * .pi
    }
    }
    // ހ౓(radian) -> ֯౓(degree)
    var degreeValue: Double {
    if #available(iOS 10.0, *) {
    let rm = Measurement(value: self, unit: UnitAngle.radians)
    let dm = rm.converted(to: .degrees)
    return dm.value
    } else {
    return self * 180 / .pi
    }
    }
    }

    View Slide

  16. αϯϓϧͷԁάϥϑͷඳ͖ํΛൈਮ
    // 0࣌ͷҐஔ͔Β։࢝
    var startRad: Double = -90.0.radianValue
    for (index, data) in graphData.enumerated() {
    // ׂ߹
    let ratio: Double = 360 * (data.value / graphDataTotal)
    // ϥδΞϯʢུɿradʣ
    let rad: Double = ratio.radianValue
    // ઔܕͷ൒ܘ
    let arcRadius: Double = (index == selectedIndex) ? radius * 1.1 : radius
    // ઔܕ
    let arcPath = UIBezierPath(arcCenter: .zero,
    radius: CGFloat(arcRadius),
    startAngle: CGFloat(startRad),
    endAngle: CGFloat(startRad + rad),
    clockwise: true)
    arcPath.addLine(to: .zero)
    arcPath.close()
    arcLayers[index].path = arcPath.cgPath
    startRad += rad
    }

    View Slide

  17. ඞཁͳͱ͖ʹ֯౓͔Βހ౓ʹม׵͢Δ
    // 0࣌ͷҐஔ͔Β։࢝
    var startRad: Double = -90.0.radianValue
    for (index, data) in graphData.enumerated() {
    // ׂ߹
    let ratio: Double = 360 * (data.value / graphDataTotal)
    // ϥδΞϯʢུɿradʣ
    let rad: Double = ratio.radianValue
    // ઔܕͷ൒ܘ
    let arcRadius: Double = (index == selectedIndex) ? radius * 1.1 : radius
    // ઔܕ
    let arcPath = UIBezierPath(arcCenter: .zero,
    radius: CGFloat(arcRadius),
    startAngle: CGFloat(startRad),
    endAngle: CGFloat(startRad + rad),
    clockwise: true)
    arcPath.addLine(to: .zero)
    arcPath.close()
    arcLayers[index].path = arcPath.cgPath
    startRad += rad
    }

    View Slide

  18. σʔλϥϕϧͷඳը

    View Slide

  19. σʔλϥϕϧͷҐஔ͕஌Γ͍ͨ

    View Slide

  20. Ґஔ͸ઔܕͷத৺͕ݟӫ͑ྑͦ͞͏
    $(1PJOU͸ʁ
    $(1PJOU͸ʁ

    View Slide

  21. TJOͰZ࣠ DPTͰY࣠ʢۃ࠲ඪˠ௚ަ࠲ඪʣ
    sinθ =
    y
    r
    → y = r * sinθ
    cosθ =
    x
    r
    → x = r * cosθ
    В
    y
    x





    S
    Y
    Z
    Y Z

    View Slide

  22. ͭ·Γɺத৺͔Βͷ֯౓ͱ൒ܘͰҐஔ͕Θ͔Δ
    func arcTextPoint(arcCenter: CGPoint,
    radian: Double,
    radius: Double) -> CGPoint {
    // ൒ܘͷେ͖͞Ͱԁͷ಺΍֎ʹςΩετΛϨΠΞ΢τ
    let x = radius * cos(radian)
    let y = radius * sin(radian)
    return CGPoint(x: arcCenter.x + CGFloat(x),
    y: arcCenter.y + CGFloat(y))
    }

    View Slide

  23. ίʔυͰɺTJOͰZ࣠ DPTͰY࣠
    func arcTextPoint(arcCenter: CGPoint,
    radian: Double,
    radius: Double) -> CGPoint {
    // ൒ܘͷେ͖͞Ͱԁͷ಺΍֎ʹςΩετΛϨΠΞ΢τ
    let x = radius * cos(radian)
    let y = radius * sin(radian)
    return CGPoint(x: arcCenter.x + CGFloat(x),
    y: arcCenter.y + CGFloat(y))
    }

    View Slide

  24. Ͱ͸ɺ࠾୒ϥϕϧΛઔܕͷத৺ʹ഑ஔ͢Δʹ͸ʁ

    View Slide

  25. தԝͷന͍ԁͷ൒ܘ͕ԁάϥϑͷഒͷ৔߹
    ̍

    View Slide

  26. ࠾୒ϥϕϧͷ൒ܘ͸

    ̓ ̍

    View Slide

  27. ࠾୒ϥϕϧͷ֯౓͸ɺ࠾୒άϥϑͷ֯౓ͷ൒෼
    ̓ ̍

    View Slide

  28. ࠾୒ϥϕϧͷ֯౓ͱ൒ܘ͕Θ͔ͬͨ
    ̓ ̍

    View Slide

  29. αϯϓϧͷσʔλϥϕϧͷॻ͖ํΛൈਮ
    // 0࣌ͷҐஔ͔Β։࢝
    var startRad: Double = -90.0.radianValue
    for (index, data) in graphData.enumerated() {
    // ׂ߹
    let ratio: Double = 360 * (data.value / graphDataTotal)
    // ϥδΞϯʢུɿradʣ
    let rad: Double = ratio.radianValue
    // ઔܕͷςΩετͷҐஔ
    let arcTextPoint: CGPoint = self.arcTextPoint(arcCenter: .zero,
    radian: startRad + rad / 2,
    radius: radius * 0.7)
    // ςΩετඳը
    arcTextLayers[index].position = arcTextPoint
    startRad += rad
    }

    View Slide

  30. ൒ܘΛഒɺ֯౓Λ൒෼ʹ͢Δ
    // 0࣌ͷҐஔ͔Β։࢝
    var startRad: Double = -90.0.radianValue
    for (index, data) in graphData.enumerated() {
    // ׂ߹
    let ratio: Double = 360 * (data.value / graphDataTotal)
    // ϥδΞϯʢུɿradʣ
    let rad: Double = ratio.radianValue
    // ઔܕͷςΩετͷҐஔ
    let arcTextPoint: CGPoint = self.arcTextPoint(arcCenter: .zero,
    radian: startRad + rad / 2,
    radius: radius * 0.7)
    // ςΩετඳը
    arcTextLayers[index].position = arcTextPoint
    startRad += rad
    }

    View Slide

  31. ͞Βʹ൒ܘͷࢉग़Λެࣜʹ͓ͯ͘͠
    // 0࣌ͷҐஔ͔Β։࢝
    var startRad: Double = -90.0.radianValue
    for (index, data) in graphData.enumerated() {
    // ׂ߹
    let ratio: Double = 360 * (data.value / graphDataTotal)
    // ϥδΞϯʢུɿradʣ
    let rad: Double = ratio.radianValue
    // ઔܕͷςΩετͷҐஔ
    let arcTextRadius: Double = (radius + radius * centerCircleRadiusScale) / 2
    let arcTextPoint: CGPoint = self.arcTextPoint(arcCenter: .zero,
    radian: startRad + rad / 2,
    radius: arcTextRadius)
    // ςΩετඳը
    arcTextLayers[index].position = arcTextPoint
    startRad += rad
    }

    View Slide

  32. Ξχϝʔγϣϯ

    View Slide

  33. ϧʔϨοτͷΑ͏ʹճస͢ΔΞχϝʔγϣϯ

    View Slide

  34. $"#BTJD"OJNBUJPOͰରԠ
    // ԁάϥϑ
    let keyPath = "transform.rotation"
    let animation = CABasicAnimation(keyPath: keyPath)
    animation.beginTime = 0
    animation.fromValue = 0.0.radianValue
    animation.toValue = 360.0.radianValue
    animation.duration = 0
    animation.speed = 0.5
    animation.repeatCount = .infinity
    animation.autoreverses = false
    // ఀࢭޙͷΞχϝʔγϣϯঢ়ଶΛҡ࣋
    animation.isRemovedOnCompletion = false
    animation.fillMode = kCAFillModeForwards
    let key = "rotationAnimation"
    arcLayers.forEach {
    $0.add(animation, forKey: key)
    }

    View Slide

  35. σʔλϥϕϧ͸ճస͠ͳ͍Α͏ʹٯճస
    // ԁάϥϑͷσʔλϥϕϧ
    let keyPath = "transform.rotation"
    let reverseAnimation = CABasicAnimation(keyPath: keyPath)
    reverseAnimation.beginTime = 0
    // ٯʹճస
    reverseAnimation.fromValue = 360.0.radianValue
    reverseAnimation.toValue = 0.0.radianValue
    reverseAnimation.duration = 0
    reverseAnimation.speed = 0.5
    reverseAnimation.repeatCount = .infinity
    reverseAnimation.autoreverses = false
    reverseAnimation.isRemovedOnCompletion = false
    reverseAnimation.fillMode = kCAFillModeForwards
    let key = "rotationAnimation"
    arcTextLayers.forEach {
    $0.add(reverseAnimation, forKey: key)
    }

    View Slide

  36. ӈଆͷը໘ͷֆจࣈϥϕϧ͸ճస͠ͳ͍

    View Slide

  37. ॳճͷදࣔΞχϝʔγϣϯ

    View Slide

  38. ͪ͜Β͸$"#BTJD"OJNBUJPOͩͱ೉͍͠ɾɾɾ
    w ಺ଆͷന͍ԁ͸൒ܘ͕૿Ճ
    w ֎ଆͷԁάϥϑ͸֯౓͕૿Ճ
    w ಺ଆ͸lUSBOTGPSNTDBMFzͰ΋
    ରԠͰ͖ͦ͏͕ͩɺಉ࣌ʹ֎ଆͷ
    ԁͷ֯౓΋૿Ճͤ͞Δͷ͸ɺ
    $"#BTJD"OJNBUJPOͰ͸೉͍͠

    View Slide

  39. $"%JTQMBZ-JOLͰϦϑϨογϡϨʔτͱಉظͯ͠ඳը✍
    // Ξχϝʔγϣϯͷઃఆ
    private let curve: AnimationCurve = .ease
    private lazy var unitBezier = UnitBezier(p1: curve.p1,
    p2: curve.p2)
    @objc private func updatePathAnimation(_ sender: CADisplayLink) {
    guard let startTime = startTimeInterval else { return }
    let elapsed: Double = CACurrentMediaTime() - startTime
    // Ξχϝʔγϣϯਐḿ཰Λܭࢉ
    let progress = (elapsed > 1.0) ? 1.0 : CGFloat(elapsed / 1.0)
    let animationProgress = unitBezier.solve(t: progress)
    // άϥϑΛඳը
    setup(progress: Double(animationProgress))
    if progress >= 1.0 {
    sender.invalidate()
    }
    }

    View Slide

  40. ܦա࣌ؒʹ߹ͬͨΞχϝʔγϣϯਐḿ཰͸ʁ
    // Ξχϝʔγϣϯͷઃఆ
    private let curve: AnimationCurve = .ease
    private lazy var unitBezier = UnitBezier(p1: curve.p1,
    p2: curve.p2)
    @objc private func updatePathAnimation(_ sender: CADisplayLink) {
    guard let startTime = startTimeInterval else { return }
    let elapsed: Double = CACurrentMediaTime() - startTime
    // Ξχϝʔγϣϯਐḿ཰Λܭࢉ
    let progress = (elapsed > 1.0) ? 1.0 : CGFloat(elapsed / 1.0)
    let animationProgress = unitBezier.solve(t: progress)
    // άϥϑΛඳը
    setup(progress: Double(animationProgress))
    if progress >= 1.0 {
    sender.invalidate()
    }
    }

    View Slide

  41. ΞχϝʔγϣϯͷΠʔδϯάΛࣗ༝ʹ࡞Δ
    ࢀߟIUUQTUFDITUBSUUPEBZUFDIDPNFOUSZJPT@BOJNBUJPO@FBTJOH
    w !8PSME%PXO5PXO͞ΜͷهࣄΛࢀর
    w $".FEJB5JNJOH'VODUJPOͱಉ͡Α͏ʹɺΞχϝʔγϣϯ
    ͷܦա࣌ؒΛݩʹΞχϝʔγϣϯࣗମͷਐḿ཰Λܭࢉ
    w 8FC,JUͷϕδΣۂઢͷ$࣮૷Λ4XJGUͰॻ͖׵͑ͯରԠ
    // Ξχϝʔγϣϯਐḿ཰Λܭࢉ
    let progress = (elapsed > 1.0) ? 1.0 : CGFloat(elapsed / 1.0)
    let animationProgress = unitBezier.solve(t: progress)
    // ਐḿ཰Λ΋ͱʹͯ͠άϥϑΛඳը
    setup(progress: Double(animationProgress))

    View Slide

  42. αϯϓϧͷॻ͖ํΛൈਮ
    // தԝʹന͍ԁʢ֎ଆͷԁͷ0.4ഒͷେ͖͞ʣΛஔ͘
    let centerCirclePath = UIBezierPath(arcCenter: .zero,
    radius: CGFloat(radius * 0.4 * progress),
    startAngle: CGFloat(-90.0.radianValue),
    endAngle: CGFloat(270.0.radianValue),
    clockwise: true)
    centerCirclePath.addLine(to: .zero)
    centerCirclePath.close()
    // ׂ߹
    let ratio: Double = 360 * (data.value / graphDataTotal)
    // ϥδΞϯʢུɿradʣ
    let rad: Double = ratio.radianValue * progress

    View Slide

  43. Ξχϝʔγϣϯͷਐḿ཰Λ͔͚Δ
    // தԝʹന͍ԁʢ֎ଆͷԁͷ0.4ഒͷେ͖͞ʣΛஔ͘
    let centerCirclePath = UIBezierPath(arcCenter: .zero,
    radius: CGFloat(radius * 0.4 * progress),
    startAngle: CGFloat(-90.0.radianValue),
    endAngle: CGFloat(270.0.radianValue),
    clockwise: true)
    centerCirclePath.addLine(to: .zero)
    centerCirclePath.close()
    // ׂ߹
    let ratio: Double = 360 * (data.value / graphDataTotal)
    // ϥδΞϯʢུɿradʣ
    let rad: Double = ratio.radianValue * progress

    View Slide

  44. δΣενϟʔ

    View Slide

  45. 6*5BQ(FTUVSF3FDPHOJ[FS

    View Slide

  46. 6*5BQ(FTUVSF3FDPHOJ[FS

    View Slide

  47. 6*5BQ(FTUVSF3FDPHOJ[FS

    View Slide

  48. 6*5BQ(FTUVSF3FDPHOJ[FS

    View Slide

  49. // ը໘ͷத৺Ͱ͸ͳ͘άϥϑͷத৺ʹ߹ΘͤͯλοϓҐஔΛม׵
    let centerOffset = CGAffineTransform(translationX: -centerPoint.x,
    y: -centerPoint.y)
    let tappedPoint: CGPoint = gesture.location(in: self)
    .applying(centerOffset)
    if isDonuts {
    guard let centerCirclePath = centerCircleLayer?.path,
    !centerCirclePath.contains(tappedPoint) else {
    // தԝͷԁΛλοϓ
    resetNeedsTransform()
    return
    }
    }
    αϯϓϧͷॻ͖ํΛൈਮ

    View Slide

  50. // ը໘ͷத৺Ͱ͸ͳ͘άϥϑͷத৺ʹ߹ΘͤͯλοϓҐஔΛม׵
    let centerOffset = CGAffineTransform(translationX: -centerPoint.x,
    y: -centerPoint.y)
    let tappedPoint: CGPoint = gesture.location(in: self)
    .applying(centerOffset)
    if isDonuts {
    guard let centerCirclePath = centerCircleLayer?.path,
    !centerCirclePath.contains(tappedPoint) else {
    // தԝͷԁΛλοϓ
    resetNeedsTransform()
    return
    }
    }
    த৺͔Βλοϓ࠲ඪΛͣΒ͢ʁ

    View Slide

  51. TVCMBZFSͷݪ఺͸MBZPVU4VCWJFXT
    Ͱௐ੔
    override func layoutSubviews() {
    super.layoutSubviews()
    arcLayers.forEach {
    $0.frame.origin = centerPoint
    }
    centerCircleLayer?.frame.origin = centerPoint
    }

    View Slide

  52. // ઔܕ
    let arcPath = UIBezierPath(arcCenter: .zero,
    radius: CGFloat(arcRadius),
    startAngle: CGFloat(startRad),
    endAngle: CGFloat(startRad + rad),
    clockwise: true)
    arcPath.addLine(to: .zero)
    arcPath.close()
    // தԝʹന͍ԁΛஔ͘
    let centerCirclePath = UIBezierPath(arcCenter: .zero,
    radius: CGFloat(radius * 0.4),
    startAngle: CGFloat(-90.0.radianValue),
    endAngle: CGFloat(270.0.radianValue),
    clockwise: true)
    centerCirclePath.addLine(to: .zero)
    centerCirclePath.close()
    QBUI͸ݪ఺ʹඳը͢Δ

    View Slide

  53. arcLayers[index].path = arcPath.cgPath
    centerCircleLayer?.path = centerCirclePath.cgPath
    ݪ఺ʹඳ͍ͨQBUIΛTVCMBZFSQBUIʹίϐʔ

    View Slide

  54. λοϓͷ࠲ඪ͸ը໘Ͱ͸ͳ͘QBUIʹͦͬͯͣΒ͢

    // ը໘ͷத৺Ͱ͸ͳ͘άϥϑͷத৺ʹ߹ΘͤͯλοϓҐஔΛม׵
    let centerOffset = CGAffineTransform(translationX: -centerPoint.x,
    y: -centerPoint.y)
    let tappedPoint: CGPoint = gesture.location(in: self)
    .applying(centerOffset)
    if isDonuts {
    guard let centerCirclePath = centerCircleLayer?.path,
    !centerCirclePath.contains(tappedPoint) else {
    // தԝͷԁΛλοϓ
    resetNeedsTransform()
    return
    }
    }

    View Slide

  55. ͞ΒʹɺΞχϝʔγϣϯͰ࠲ඪ͕มԽͨ͠ͱ͖͸ʁ
    w TVCMBZFSQBUI͸ίϐʔͳͷ
    ͰΞχϝʔγϣϯޙͷมߋ
    ͕൓ө͞Εͳ͍

    View Slide


  56. Ξχϝʔγϣϯޙͷঢ়ଶ͸QSFTFOUBUJPO

    // ແݶճస͕ఀࢭͨ͠ঢ়ଶͷϨΠϠʔ
    guard let presentation = layer.presentation() else {
    return
    }
    // ແݶճస͕ఀࢭͨ͠ϨΠϠʔ࠲ඪʹม׵ͯ͠ઔܕͷίϐʔΛ࠶࡞੒
    let copyPath = UIBezierPath(cgPath: layerPath)
    copyPath.apply(presentation.affineTransform())
    if copyPath.contains(tappedPoint) {
    // ઔܕΛλοϓ
    selectedIndex = (index == selectedIndex) ? nil : index
    // άϥϑΛ࠶ඳը
    setup(progress: 1.0,
    rotationRadian: Double(currentRotationRadian))
    return
    }

    View Slide


  57. ΞχϝʔγϣϯޙͷQBUIΛੜ੒ͯ͠൑ఆ
    // ແݶճస͕ఀࢭͨ͠ঢ়ଶͷϨΠϠʔ
    guard let presentation = layer.presentation() else {
    return
    }
    // ແݶճస͕ఀࢭͨ͠ϨΠϠʔ࠲ඪʹม׵ͯ͠ઔܕͷίϐʔΛ࠶࡞੒
    let copyPath = UIBezierPath(cgPath: layerPath)
    copyPath.apply(presentation.affineTransform())
    if copyPath.contains(tappedPoint) {
    // ઔܕΛλοϓ
    selectedIndex = (index == selectedIndex) ? nil : index
    // άϥϑΛ࠶ඳը
    setup(progress: 1.0,
    rotationRadian: Double(currentRotationRadian))
    return
    }

    View Slide

  58. 6*1BO(FTUVSF3FDPHOJ[FS


    View Slide

  59. 6*1BO(FTUVSF3FDPHOJ[FS


    View Slide

  60. υϥοά։͔࢝Βऴྃ·Ͱͷ֯౓͸ʁ


    View Slide

  61. ઌʹ݁࿦Ͱ͕͢ɺ఺ͷ܏͖͸BUBOͰٻΊΒΕΔ

    View Slide

  62. UBO BUBO BUBOͯɾɾɾ
    ࢀߟIUUQTRJJUBDPNLJNJ@ESPQDJUFNT
    FEDFCECEEBDPNNFOUBEFE
    w !LPIFSʢ͜ͻʔʣ͞Μͷίϝϯτ͕ࢀߟʹͳΓ·͢
    w UBOВ͸Z࣠ͱY࣠ͷൺʢϥδΞϯʣ
    w BUBO͸UBOͷٯؔ਺
    w BUBO͸൒प෼ʢ౓ʙ౓ʣͷൣғΛฦ͠ɺBUBO
    ͸̍प෼ʢ౓ʙ౓ʣͷൣғΛฦ͢

    View Slide

  63. UBOВ͸Z࣠ͱY࣠ͷൺʢϥδΞϯʣ
    y
    x

    ୈ৅ݶ
    ୈ৅ݶ
    ୈ৅ݶ ୈ৅ݶ
    ›
    ›
    w ౓ͷͱ͖ZY

    View Slide

  64. BUBO͸UBOͷٯؔ਺

    View Slide

  65. BUBO͸൒प෼ʢ౓ʙ౓ʣ
    y
    x





    ୈ৅ݶ
    ୈ৅ݶ
    ୈ৅ݶ ୈ৅ݶ
    SBE


    View Slide

  66. BUBO͸൒प෼ʢ౓ʙ౓ʣͷൣғΛฦ͢
    ౓Ͱ͸౓Ͱճసͤ͞Δͱ͖ʹࠔΔ

    View Slide

  67. BUBO͸̍प෼ʢ౓ʙ౓ʣ
    y
    x





    ୈ৅ݶ
    ୈ৅ݶ
    ୈ৅ݶ ୈ৅ݶ
    SBE


    View Slide

  68. BUBO͸̍प෼ʢ౓ʙ౓ʣͷൣғΛฦ͢

    View Slide

  69. ̎఺ͷBUBOͷࠩ෼ͰɺυϥοΫͨ֯͠౓Λܭࢉ

    View Slide

  70. άϥϑͰٻΊΒΕͦ͏ͳཁ݅Λୡ੒
    άϥϑͷඳը
    σʔλϥϕϧͷඳը
    Ξχϝʔγϣϯ
    δΣενϟʔ

    View Slide

  71. ·ͱΊ
    w ࡾ֯ؔ਺ͱހ౓๏͸άϥϑΛඳ্͘Ͱඞཁͳ஌ࣝͰ͢ʂ
    w ͜ΕͰϥΠϒϥϦʹཔΒͣάϥϑΛࣗ࡞͢Δ͜ͱ΋ग़དྷͦ͏
    Ͱ͢Ͷʂ
    w Ϣʔβʔ͕تͿૉ੖Β͍͠άϥϑΛσβΠϯ͍ͨ͠ਓ45ɺ
    ࣮૷͍ͨ͠ਓ67͸ɺͥͻฐࣾͰҰॹʹಇ͖͠·͠ΐ͏

    View Slide

  72. IUUQTHJUIVCDPNNBTBTIJTVUPV1JF(SBQI4BNQMFS
    ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠

    View Slide