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

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. άϥϑͷલʹԁʢ୯Ґԁʣͷ͓͞Β͍ w ୈʙ̐৅ݶͷ஌͕ࣝඞཁ w ๮ંΕઢάϥϑ͸ୈ̍৅ݶͰ͋ Δ͜ͱ͕ଟ͘ɺඳը͸ͦΕ΄Ͳ ೉͘͠ͳ͍ w TJOВ DPTВ

    UBOВʢࡾ֯ؔ਺ʣɺ ϥδΞϯʢݽ౓๏ʣ w $PSF(SBQIJDT $PSF"OJNBUJPO y x      ୈ৅ݶ ୈ৅ݶ ୈ৅ݶ ୈ৅ݶ SBE
  2. "OHMFͳͷͰ֯౓ͩͳͱࢥ͏͚Ͳ // endAngle ͸ π / 2 = 90౓ let

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

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

    path = UIBezierPath(arcCenter: .zero, radius: 100, startAngle: 0, endAngle: endAngle, clockwise: true)
  5. 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 } } }
  6. αϯϓϧͷԁάϥϑͷඳ͖ํΛൈਮ // 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 }
  7. ඞཁͳͱ͖ʹ֯౓͔Βހ౓ʹม׵͢Δ // 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 }
  8. TJOͰZ࣠ DPTͰY࣠ʢۃ࠲ඪˠ௚ަ࠲ඪʣ sinθ = y r → y = r

    * sinθ cosθ = x r → x = r * cosθ В y x      S Y Z Y Z
  9. ͭ·Γɺத৺͔Βͷ֯౓ͱ൒ܘͰҐஔ͕Θ͔Δ 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)) }
  10. ίʔυͰɺ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)) }
  11. αϯϓϧͷσʔλϥϕϧͷॻ͖ํΛൈਮ // 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 }
  12. ൒ܘΛഒɺ֯౓Λ൒෼ʹ͢Δ // 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 }
  13. ͞Βʹ൒ܘͷࢉग़Λެࣜʹ͓ͯ͘͠ // 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 }
  14. $"#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) }
  15. σʔλϥϕϧ͸ճస͠ͳ͍Α͏ʹٯճస // ԁάϥϑͷσʔλϥϕϧ 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) }
  16. $"%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() } }
  17. ܦա࣌ؒʹ߹ͬͨΞχϝʔγϣϯਐḿ཰͸ʁ // Ξχϝʔγϣϯͷઃఆ 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() } }
  18. ΞχϝʔγϣϯͷΠʔδϯάΛࣗ༝ʹ࡞Δ ࢀߟ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))
  19. αϯϓϧͷॻ͖ํΛൈਮ // தԝʹന͍ԁʢ֎ଆͷԁͷ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
  20. Ξχϝʔγϣϯͷਐḿ཰Λ͔͚Δ // தԝʹന͍ԁʢ֎ଆͷԁͷ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
  21. // ը໘ͷத৺Ͱ͸ͳ͘άϥϑͷத৺ʹ߹ΘͤͯλοϓҐஔΛม׵ 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 } } αϯϓϧͷॻ͖ํΛൈਮ
  22. // ը໘ͷத৺Ͱ͸ͳ͘άϥϑͷத৺ʹ߹ΘͤͯλοϓҐஔΛม׵ 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 } } த৺͔Βλοϓ࠲ඪΛͣΒ͢ʁ
  23. // ઔܕ 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͸ݪ఺ʹඳը͢Δ
  24. λοϓͷ࠲ඪ͸ը໘Ͱ͸ͳ͘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 } }
  25. Ξχϝʔγϣϯޙͷঢ়ଶ͸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 }
  26. Ξχϝʔγϣϯޙͷ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 }