Masashi-Sutou
September 02, 2018
3k

# iOSでグラフを描くために必要な知識

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

## Masashi-Sutou

September 02, 2018

## Transcript

6. ### άϥϑͷલʹԁʢ୯Ґԁʣͷ͓͞Β͍ w ୈʙ̐৅ݶͷ஌͕ࣝඞཁ w ๮ંΕઢάϥϑ͸ୈ̍৅ݶͰ͋ Δ͜ͱ͕ଟ͘ɺඳը͸ͦΕ΄Ͳ ೉͘͠ͳ͍ w TJOВ DPTВ

UBOВʢࡾ֯ؔ਺ʣɺ ϥδΞϯʢݽ౓๏ʣ w \$PSF(SBQIJDT \$PSF"OJNBUJPO y x      ୈ৅ݶ ୈ৅ݶ ୈ৅ݶ ୈ৅ݶ SBE

12. ### "OHMFͳͷͰ֯౓ͩͳͱࢥ͏͚Ͳ // endAngle ͸ π / 2 = 90౓ let

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

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

path = UIBezierPath(arcCenter: .zero, radius: 100, startAngle: 0, endAngle: endAngle, clockwise: true)
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 } } }

21. ### TJOͰZ࣠ DPTͰY࣠ʢۃ࠲ඪˠ௚ަ࠲ඪʣ sinθ = y r → y = r

* sinθ cosθ = x r → x = r * cosθ В y x      S Y Z Y Z
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)) }
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)) }

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

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() } }
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() } }
41. ### ΞχϝʔγϣϯͷΠʔδϯάΛࣗ༝ʹ࡞Δ ࢀߟIUUQTUFDITUBSUUPEBZUFDIDPNFOUSZJPT@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))

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 } } αϯϓϧͷॻ͖ํΛൈਮ
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 } } த৺͔Βλοϓ࠲ඪΛͣΒ͢ʁ
51. ### TVCMBZFSͷݪ఺͸MBZPVU4VCWJFXT Ͱௐ੔ override func layoutSubviews() { super.layoutSubviews() arcLayers.forEach { \$0.frame.origin

= centerPoint } centerCircleLayer?.frame.origin = centerPoint }

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

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

62. ### UBO BUBO BUBOͯɾɾɾ ࢀߟIUUQTRJJUBDPNLJNJ@ESPQDJUFNT FEDFCECEEBDPNNFOUBEFE w !LPIFSʢ͜ͻʔʣ͞Μͷίϝϯτ͕ࢀߟʹͳΓ·͢ w UBOВ͸Z࣠ͱY࣠ͷൺʢϥδΞϯʣ w

BUBO͸UBOͷٯؔ਺ w BUBO͸൒प෼ʢ౓ʙ౓ʣͷൣғΛฦ͠ɺBUBO ͸̍प෼ʢ౓ʙ౓ʣͷൣғΛฦ͢
63. ### UBOВ͸Z࣠ͱY࣠ͷൺʢϥδΞϯʣ y x  ୈ৅ݶ ୈ৅ݶ ୈ৅ݶ ୈ৅ݶ  

w ౓ͷͱ͖ZY

65. ### BUBO͸൒प෼ʢ౓ʙ౓ʣ y x      ୈ৅ݶ ୈ৅ݶ

ୈ৅ݶ ୈ৅ݶ SBE ౓ ౓

67. ### BUBO͸̍प෼ʢ౓ʙ౓ʣ y x      ୈ৅ݶ ୈ৅ݶ

ୈ৅ݶ ୈ৅ݶ SBE ౓ ౓