├── CircleMenu.podspec ├── CircleMenu ├── Circle.swift ├── CircleGestureRecognizer.swift ├── CircleIconView.swift ├── CircleOverlayView.swift └── CircleThumb.swift ├── CircleMenuDemo ├── CircleMenuDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── sufialhussaini.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── sufialhussaini.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── CircleMenuDemo.xcscheme │ │ └── xcschememanagement.plist └── CircleMenuDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── icon_arrow_up.imageset │ │ ├── Contents.json │ │ └── icon_arrow_up.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CustomViewController.swift │ ├── DefaultNonRotatingViewController.swift │ ├── DefaultRotatingViewController.swift │ └── Info.plist ├── LICENSE.md └── README.md /CircleMenu.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Swift-CircleMenu" 3 | s.version = "1.0.0" 4 | s.summary = "Rotating circle menu written in Swift 3" 5 | s.description = "Rotating circle menu written in Swift 3. Features include customizable rotatability & inertia effect." 6 | s.homepage = "https://github.com/Sufi-Al-Hussaini/Swift-CircleMenu" 7 | s.license = { :type => "MIT", :file => "LICENSE" } 8 | s.author = { "Shoaib Ahmed / Sufi-Al-Hussaini" => "sufialhussaini@gmail.com" } 9 | s.social_media_url = "http://shoaib-ahmed.com" 10 | s.source = { :git => "https://github.com/Sufi-Al-Hussaini/Swift-CircleMenu.git", :tag => s.version } 11 | 12 | s.platforms = { :ios => "8.0" } 13 | s.requires_arc = true 14 | 15 | s.source_files = 'CircleMenu/*.{swift}' 16 | 17 | end -------------------------------------------------------------------------------- /CircleMenu/Circle.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2016 Shoaib Ahmed / Sufi-Al-Hussaini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | */ 11 | // 12 | // Circle.swift 13 | // CircleMenu 14 | // 15 | // Created by Shoaib Ahmed on 11/25/16. 16 | // Copyright © 2016 Shoaib Ahmed / Sufi-Al-Hussaini. All rights reserved. 17 | // 18 | 19 | import UIKit 20 | 21 | class Circle: UIView { 22 | 23 | enum ThumbSeparatorStyle { 24 | case none 25 | case basic 26 | } 27 | 28 | let kRotationDegrees: CGFloat = 90 29 | 30 | public var thumbs: NSMutableArray = [] 31 | public var circle: UIBezierPath? 32 | public var path: UIBezierPath? 33 | public var ringWidth: CGFloat 34 | // public var isOverlayed: Bool = true 35 | public var isInertiaEffect: Bool = true 36 | private(set) var isRotate: Bool = true 37 | public var numberOfSegments: Int 38 | public var circleColor: UIColor? 39 | public var separatorColor: UIColor? 40 | public var separatorStyle: ThumbSeparatorStyle 41 | public var recognizer: CircleGestureRecognizer = CircleGestureRecognizer() 42 | public var overlayView: CircleOverlayView? 43 | 44 | public var delegate: CircleDelegate? 45 | public var dataSource: CircleDataSource? 46 | 47 | //Circle radius is equal to rect / 2 , path radius is equal to rect1/2. 48 | required init(with frame: CGRect, numberOfSegments segments: Int, ringWidth width: CGFloat, isRotating rotate: Bool = true, iconWidth: CGFloat = CircleThumb.kIconViewWidth, iconHeight: CGFloat = 30) { 49 | self.ringWidth = width 50 | self.numberOfSegments = segments 51 | self.separatorStyle = .none 52 | self.circleColor = UIColor.clear 53 | self.isRotate = rotate 54 | 55 | super.init(frame: frame) 56 | 57 | self.recognizer = CircleGestureRecognizer(target: self, action: #selector(tapped)) 58 | self.addGestureRecognizer(self.recognizer) 59 | self.isOpaque = false 60 | 61 | let rect1 = CGRect(x: 0, y: 0, width: frame.height - (2*ringWidth), height: frame.width - (2*ringWidth)) 62 | for _ in 0.. UIImage 153 | 154 | } 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /CircleMenu/CircleGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2016 Shoaib Ahmed / Sufi-Al-Hussaini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | */ 11 | // 12 | // CircleGestureRecognizer.swift 13 | // CircleMenu 14 | // 15 | // Created by Shoaib Ahmed on 11/25/16. 16 | // Copyright © 2016 Shoaib Ahmed / Sufi-Al-Hussaini. All rights reserved. 17 | // 18 | 19 | import UIKit 20 | import UIKit.UIGestureRecognizerSubclass 21 | 22 | class CircleGestureRecognizer: UIGestureRecognizer { 23 | 24 | private let DECELERATION_MULTIPLIER: Double = 30 25 | 26 | private var previousTouchDate: Date? 27 | private var currentTransformAngle: CFloat? 28 | 29 | public var isEnded: Bool? 30 | public var rotation: CGFloat? 31 | public var controlPoint: CGPoint? 32 | public var currentThumb: CircleThumb? 33 | 34 | 35 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 36 | let view = self.view as! Circle 37 | let touch = touches.first 38 | let point = touch?.location(in: view) 39 | 40 | // Fail when more than 1 finger detected. 41 | if event!.touches(for: self)!.count > 1 || view.path!.contains(point!) { 42 | self.state = .failed 43 | } 44 | self.isEnded = false 45 | } 46 | 47 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 48 | if self.state == .possible { 49 | state = .began 50 | } 51 | else { 52 | state = .changed 53 | } 54 | 55 | let view = self.view as! Circle 56 | if !view.isRotate { 57 | return 58 | } 59 | 60 | // We can look at any touch object since we know we 61 | // have only 1. If there were more than 1 then 62 | // touchesBegan:withEvent: would have failed the recognizer. 63 | let touch = touches.first 64 | 65 | // To rotate with one finger, we simulate a second finger. 66 | // The second figure is on the opposite side of the virtual 67 | // circle that represents the rotation gesture. 68 | let center = CGPoint(x: view.bounds.midX, y: view.bounds.midY) 69 | let currentTouchPoint = touch?.location(in: view) 70 | let previousTouchPoint = touch?.previousLocation(in: view) 71 | 72 | previousTouchDate = Date() 73 | 74 | let angleInRadians: CGFloat = atan2(currentTouchPoint!.y - center.y, currentTouchPoint!.x - center.x) - atan2(previousTouchPoint!.y - center.y, previousTouchPoint!.x - center.x) 75 | self.rotation = angleInRadians 76 | currentTransformAngle = atan2f(Float(view.transform.b), Float(view.transform.a)) 77 | } 78 | 79 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 80 | let view = self.view as! Circle 81 | 82 | // Handle non-rotating circle menu separately 83 | if !view.isRotate { 84 | if self.state != .changed { 85 | let touch = touches.first 86 | for thumb in view.thumbs { 87 | let thumb = thumb as! CircleThumb 88 | let touchPoint = touch?.location(in: thumb) 89 | if thumb.arc!.cgPath.contains(touchPoint!) { 90 | self.currentThumb?.iconView!.isSelected = false 91 | thumb.iconView!.isSelected = true 92 | self.currentThumb = thumb 93 | 94 | view.delegate?.circle(view, didMoveTo: thumb.tag, thumb: thumb) 95 | } 96 | } 97 | } 98 | else { 99 | currentTransformAngle = 0 100 | state = .ended 101 | } 102 | return 103 | } 104 | 105 | // Perform final check to make sure a tap was not misinterpreted. 106 | if self.state == .changed { 107 | let view = self.view as! Circle 108 | var flipintime: Double = 0 109 | var angle: Double = 0 110 | 111 | if view.isInertiaEffect { 112 | let angleInRadians: CGFloat = atan2(view.transform.b, view.transform.a) - CGFloat(currentTransformAngle!) 113 | let time: Double = Date().timeIntervalSince(previousTouchDate!) 114 | let velocity: Double = Double(angleInRadians)/time 115 | let a: Double = DECELERATION_MULTIPLIER 116 | 117 | flipintime = fabs(velocity)/a 118 | angle = (velocity * flipintime) - (a * flipintime * flipintime/2) 119 | 120 | if (angle>M_PI/2) || (angle<0 && angle < (-M_PI/2)) { 121 | if angle < 0 { angle = -M_PI/2.1 } 122 | else { angle = M_PI/2.1 } 123 | 124 | flipintime = 1 / -(a/2*velocity/angle) 125 | } 126 | } 127 | 128 | UIView.animate(withDuration: flipintime, delay: 0, options: .curveEaseOut, animations: { 129 | view.transform = CGAffineTransform(rotationAngle: CGFloat(angle)) 130 | }, completion: { (finished: Bool) in 131 | for thumb in view.thumbs { 132 | let thumb = thumb as! CircleThumb 133 | 134 | let point = thumb.convert(thumb.centerPoint!, to: nil) 135 | let shadow: CircleThumb = view.overlayView!.overlayThumb! 136 | let shadowRect: CGRect = shadow.superview!.convert(shadow.frame, to: nil) 137 | 138 | if shadowRect.contains(point) { 139 | let deltaAngle: CGFloat = -CircleThumb.radiansFrom(degrees: 180) + atan2(view.transform.a, view.transform.b) + atan2(thumb.transform.a, thumb.transform.b) 140 | 141 | let current = view.transform 142 | 143 | UIView.animate(withDuration: 0.3, animations: { 144 | view.transform = current.rotated(by: deltaAngle) 145 | }) 146 | 147 | 148 | self.currentThumb?.iconView!.isSelected = false 149 | thumb.iconView!.isSelected = true 150 | self.currentThumb = thumb 151 | 152 | view.delegate?.circle(view, didMoveTo: thumb.tag, thumb: thumb) 153 | self.isEnded = true 154 | break 155 | } 156 | 157 | } 158 | }) 159 | 160 | currentTransformAngle = 0 161 | state = .ended 162 | } 163 | else { 164 | let touch = touches.first 165 | 166 | // Circle rotation animation code, to move selected thumb to center top position 167 | for thumb in view.thumbs { 168 | let thumb = thumb as! CircleThumb 169 | let touchPoint = touch?.location(in: thumb) 170 | if thumb.arc!.cgPath.contains(touchPoint!) { 171 | let deltaAngle: CGFloat = -CircleThumb.radiansFrom(degrees: 180) + atan2(view.transform.a, view.transform.b) + atan2(thumb.transform.a, thumb.transform.b) 172 | 173 | let current = view.transform 174 | 175 | UIView.animate(withDuration: 0.3, animations: { 176 | view.transform = current.rotated(by: deltaAngle) 177 | }, completion: { (finished: Bool) in 178 | self.currentThumb?.iconView!.isSelected = false 179 | thumb.iconView!.isSelected = true 180 | self.currentThumb = thumb 181 | 182 | view.delegate?.circle(view, didMoveTo: thumb.tag, thumb: thumb) 183 | self.isEnded = true 184 | }) 185 | 186 | break 187 | } 188 | } 189 | 190 | self.state = .failed 191 | } 192 | } 193 | 194 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 195 | self.state = .failed 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /CircleMenu/CircleIconView.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2016 Shoaib Ahmed / Sufi-Al-Hussaini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | */ 11 | // 12 | // CircleIconView.swift 13 | // CircleMenu 14 | // 15 | // Created by Shoaib Ahmed on 11/26/16. 16 | // Copyright © 2016 Shoaib Ahmed / Sufi-Al-Hussaini. All rights reserved. 17 | // 18 | 19 | import UIKit 20 | 21 | class CircleIconView: UIView { 22 | 23 | public var image: UIImage? 24 | public var highlightedIconColor = UIColor.green 25 | public var isSelected: Bool { 26 | didSet { 27 | if oldValue != isSelected { 28 | self.setNeedsDisplay() 29 | } 30 | } 31 | } 32 | 33 | 34 | override init(frame: CGRect) { 35 | isSelected = false 36 | 37 | super.init(frame: frame) 38 | 39 | isOpaque = false 40 | backgroundColor = UIColor.clear 41 | } 42 | 43 | required init?(coder aDecoder: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | 47 | override func draw(_ rect: CGRect) { 48 | super.draw(rect) 49 | guard image != nil else { 50 | return 51 | } 52 | 53 | if isSelected { 54 | let context = UIGraphicsGetCurrentContext() 55 | context!.translateBy(x: 0, y: image!.size.height) 56 | context!.scaleBy(x: 1.0, y: -1.0) 57 | context!.setBlendMode(CGBlendMode.color) 58 | context!.clip(to: self.bounds, mask: image!.cgImage!) // this restricts drawing to within alpha channel 59 | context!.setFillColor(self.highlightedIconColor.cgColor) // this is your color, a light reddish tint 60 | context!.fill(rect) 61 | } 62 | else { 63 | image?.draw(in: rect) 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /CircleMenu/CircleOverlayView.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2016 Shoaib Ahmed / Sufi-Al-Hussaini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | */ 11 | // 12 | // CircleOverlayView.swift 13 | // CircleMenu 14 | // 15 | // Created by Shoaib Ahmed on 11/26/16. 16 | // Copyright © 2016 Shoaib Ahmed / Sufi-Al-Hussaini. All rights reserved. 17 | // 18 | 19 | import UIKit 20 | 21 | class CircleOverlayView: UIView { 22 | 23 | public var overlayThumb: CircleThumb! 24 | public var circle: Circle? 25 | public var controlPoint: CGPoint? 26 | public var buttonCenter: CGPoint? 27 | 28 | open override var center: CGPoint { 29 | didSet { 30 | self.circle?.center = buttonCenter! 31 | } 32 | } 33 | 34 | required init?(with circle: Circle) { 35 | if !circle.isRotate { 36 | fatalError("init(with:) called for non-rotating circle") 37 | } 38 | 39 | let frame = circle.frame 40 | super.init(frame: frame) 41 | 42 | self.isOpaque = false 43 | self.circle = circle 44 | self.circle?.overlayView = self 45 | 46 | var rect1 = CGRect(x: 0, y: 0, width: self.circle!.frame.height - (2*circle.ringWidth), height: self.circle!.frame.width - (2*circle.ringWidth)) 47 | rect1.origin.x = self.circle!.frame.size.width/2 - rect1.size.width/2 48 | rect1.origin.y = 0 49 | 50 | overlayThumb = CircleThumb(with: rect1.size.height/2, longRadius: self.circle!.frame.size.height/2, numberOfSegments: self.circle!.numberOfSegments) 51 | overlayThumb?.isGradientFill = false 52 | 53 | overlayThumb?.layer.position = CGPoint(x: frame.width/2, y: circle.ringWidth/2) 54 | self.controlPoint = overlayThumb?.layer.position 55 | self.addSubview(overlayThumb!) 56 | 57 | overlayThumb?.arcColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 0.3) 58 | self.buttonCenter = CGPoint(x: frame.midX, y: circle.frame.midY) 59 | } 60 | 61 | required init?(coder aDecoder: NSCoder) { 62 | fatalError("init(coder:) has not been implemented") 63 | } 64 | 65 | override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 66 | return false 67 | } 68 | 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /CircleMenu/CircleThumb.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2016 Shoaib Ahmed / Sufi-Al-Hussaini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | */ 11 | // 12 | // CircleThumb.swift 13 | // CircleMenu 14 | // 15 | // Created by Shoaib Ahmed on 11/25/16. 16 | // Copyright © 2016 Shoaib Ahmed / Sufi-Al-Hussaini. All rights reserved. 17 | // 18 | 19 | import UIKit 20 | 21 | class CircleThumb: UIView { 22 | 23 | enum CGGradientPosition { 24 | case vertical 25 | case horizontal 26 | } 27 | 28 | static let kIconViewWidth: CGFloat = 30 29 | static let kIconViewHeight: CGFloat = 30 30 | 31 | private var numberOfSegments: CGFloat 32 | // private var bigArcHeight: CGFloat 33 | // private var smallArcWidth: CGFloat 34 | 35 | public let sRadius: CGFloat 36 | public let lRadius: CGFloat 37 | // public let yydifference: CGFloat? 38 | public var arc: UIBezierPath? 39 | public var separatorColor: UIColor? 40 | public var separatorStyle: Circle.ThumbSeparatorStyle 41 | public var centerPoint: CGPoint? 42 | public var colorsLocations: Array? 43 | public var isGradientFill: Bool 44 | public var gradientColors = [UIColor.black.cgColor, UIColor.gray.cgColor] 45 | public var arcColor: UIColor? 46 | 47 | public var iconView: CircleIconView! 48 | 49 | 50 | required init(with shortRadius: CGFloat, longRadius: CGFloat, numberOfSegments sNumber: Int, iconWidth: CGFloat = kIconViewWidth, iconHeight: CGFloat = kIconViewHeight) { 51 | var frame: CGRect? 52 | 53 | var width: CGFloat 54 | var height: CGFloat 55 | 56 | let fstartAngle: CGFloat = CGFloat(270 - ((360/sNumber)/2)) 57 | let fendAngle: CGFloat = CGFloat(270 + ((360/sNumber)/2)) 58 | 59 | let startAngle: CGFloat = CircleThumb.radiansFrom(degrees: fstartAngle) 60 | let endAngle: CGFloat = CircleThumb.radiansFrom(degrees: fendAngle) 61 | 62 | 63 | let bigArc = UIBezierPath(arcCenter: CGPoint(x: longRadius, y: longRadius), radius:longRadius, startAngle:startAngle, endAngle:endAngle, clockwise: true) 64 | let smallArc = UIBezierPath(arcCenter: CGPoint(x: longRadius, y: longRadius), radius:shortRadius, startAngle:startAngle, endAngle:endAngle, clockwise: true) 65 | 66 | // Start of calculations 67 | if ((fendAngle - fstartAngle) <= 180) { 68 | width = bigArc.bounds.size.width 69 | height = smallArc.currentPoint.y 70 | frame = CGRect(x: 0, y: 0, width: width, height: height) 71 | } 72 | if ((fendAngle - fstartAngle) > 269) { 73 | frame = CGRect(x: 0, y: 0, width: bigArc.bounds.size.width, height: bigArc.bounds.size.height) 74 | } 75 | //End of calculations 76 | 77 | 78 | numberOfSegments = CGFloat(sNumber) 79 | sRadius = shortRadius 80 | lRadius = longRadius 81 | separatorStyle = .none 82 | isGradientFill = false 83 | 84 | super.init(frame: frame!) 85 | 86 | self.isOpaque = false 87 | self.backgroundColor = UIColor.clear 88 | 89 | let y: CGFloat = (lRadius - sRadius)/2.00 90 | let xi: CGFloat = 0.5 91 | let yi: CGFloat = y/frame!.size.height 92 | self.layer.anchorPoint = CGPoint(x: xi, y: yi) 93 | self.isGradientFill = true 94 | self.arcColor = UIColor.green 95 | self.centerPoint = CGPoint(x: self.bounds.midX, y: y) 96 | self.iconView = CircleIconView(frame: CGRect(x: frame!.midX, y: y, width: iconWidth, height: iconHeight)) 97 | self.iconView!.center = CGPoint(x: frame!.midX, y: y) 98 | self.addSubview(self.iconView!) 99 | } 100 | 101 | required init?(coder aDecoder: NSCoder) { 102 | fatalError("init(coder:) has not been implemented") 103 | } 104 | 105 | override func draw(_ rect: CGRect) { 106 | // Drawing code 107 | super.draw(rect) 108 | arcColor?.setStroke() 109 | arcColor?.setFill() 110 | 111 | //Angles 112 | let clockwiseStartAngle = CircleThumb.radiansFrom(degrees: (270 - ((360/numberOfSegments)/2))) 113 | let clockwiseEndAngle = CircleThumb.radiansFrom(degrees: (270 + ((360/numberOfSegments)/2))) 114 | 115 | let nonClockwiseStartAngle = clockwiseEndAngle 116 | let nonClockwiseEndAngle = clockwiseStartAngle 117 | 118 | let center = CGPoint(x: rect.midX, y: lRadius) 119 | 120 | arc = UIBezierPath(arcCenter: center, radius: lRadius, startAngle: clockwiseStartAngle, endAngle: clockwiseEndAngle, clockwise: true) 121 | let f = arc?.currentPoint 122 | arc?.addArc(withCenter: center, radius: sRadius, startAngle: nonClockwiseStartAngle, endAngle: nonClockwiseEndAngle, clockwise: false) 123 | 124 | let e = arc?.currentPoint 125 | 126 | arc?.close() 127 | 128 | let context = UIGraphicsGetCurrentContext() 129 | 130 | if !self.isGradientFill { 131 | arc?.fill() 132 | } 133 | else { 134 | // Gradient color code 135 | var la = [CGFloat]() 136 | let path = arc?.cgPath 137 | if (gradientColors.count == 2) { 138 | la.insert(0.00, at: 0) 139 | la.insert(1.00, at: 1) 140 | } 141 | else 142 | { 143 | if colorsLocations == nil { 144 | for var i in (0.. Bool { 185 | return false 186 | } 187 | 188 | static func radiansFrom(degrees: CGFloat) -> CGFloat { 189 | return (CGFloat(M_PI) * (degrees) / 180.0) 190 | } 191 | 192 | static func degreesFrom(radians: CGFloat) -> CGFloat { 193 | return ((radians) / CGFloat(M_PI) * 180) 194 | } 195 | 196 | func drawLinearGradient(context: CGContext, path: CGPath, colors: CFArray, position: CGGradientPosition, locations: [CGFloat], rect: CGRect) { 197 | let colorSpace = CGColorSpaceCreateDeviceRGB() 198 | 199 | let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: locations) 200 | var startPoint: CGPoint 201 | var endPoint: CGPoint 202 | 203 | switch (position) { 204 | case .horizontal: 205 | startPoint = CGPoint(x: rect.midX, y: rect.minY) 206 | endPoint = CGPoint(x: rect.midX, y: rect.maxY) 207 | case .vertical: 208 | startPoint = CGPoint(x: rect.minX, y: rect.midY) 209 | endPoint = CGPoint(x: rect.maxX, y: rect.midY) 210 | } 211 | 212 | context.saveGState() 213 | context.addPath(path) 214 | context.clip() 215 | context.drawLinearGradient(gradient!, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(rawValue: 0)) 216 | context.restoreGState() 217 | 218 | // CGGradientRelease(gradient); 219 | // CGColorSpaceRelease(colorSpace); 220 | } 221 | 222 | 223 | } 224 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6312D3BC1E07E3330061AE14 /* Circle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6312D3B71E07E3330061AE14 /* Circle.swift */; }; 11 | 6312D3BD1E07E3330061AE14 /* CircleGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6312D3B81E07E3330061AE14 /* CircleGestureRecognizer.swift */; }; 12 | 6312D3BE1E07E3330061AE14 /* CircleIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6312D3B91E07E3330061AE14 /* CircleIconView.swift */; }; 13 | 6312D3BF1E07E3330061AE14 /* CircleOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6312D3BA1E07E3330061AE14 /* CircleOverlayView.swift */; }; 14 | 6312D3C01E07E3330061AE14 /* CircleThumb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6312D3BB1E07E3330061AE14 /* CircleThumb.swift */; }; 15 | 6367D3B71DE839AC003BAB6F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6367D3B61DE839AC003BAB6F /* AppDelegate.swift */; }; 16 | 6367D3B91DE839AC003BAB6F /* DefaultRotatingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6367D3B81DE839AC003BAB6F /* DefaultRotatingViewController.swift */; }; 17 | 6367D3BC1DE839AC003BAB6F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6367D3BA1DE839AC003BAB6F /* Main.storyboard */; }; 18 | 6367D3BE1DE839AC003BAB6F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6367D3BD1DE839AC003BAB6F /* Assets.xcassets */; }; 19 | 6367D3C11DE839AC003BAB6F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6367D3BF1DE839AC003BAB6F /* LaunchScreen.storyboard */; }; 20 | 639EDF301DF1F203006836CD /* DefaultNonRotatingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 639EDF2F1DF1F203006836CD /* DefaultNonRotatingViewController.swift */; }; 21 | 639EDF321DF1F3A8006836CD /* CustomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 639EDF311DF1F3A8006836CD /* CustomViewController.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 6312D3B71E07E3330061AE14 /* Circle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Circle.swift; sourceTree = ""; }; 26 | 6312D3B81E07E3330061AE14 /* CircleGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleGestureRecognizer.swift; sourceTree = ""; }; 27 | 6312D3B91E07E3330061AE14 /* CircleIconView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleIconView.swift; sourceTree = ""; }; 28 | 6312D3BA1E07E3330061AE14 /* CircleOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleOverlayView.swift; sourceTree = ""; }; 29 | 6312D3BB1E07E3330061AE14 /* CircleThumb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleThumb.swift; sourceTree = ""; }; 30 | 6367D3B31DE839AC003BAB6F /* CircleMenuDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CircleMenuDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 6367D3B61DE839AC003BAB6F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 32 | 6367D3B81DE839AC003BAB6F /* DefaultRotatingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultRotatingViewController.swift; sourceTree = ""; }; 33 | 6367D3BB1DE839AC003BAB6F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 34 | 6367D3BD1DE839AC003BAB6F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35 | 6367D3C01DE839AC003BAB6F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 36 | 6367D3C21DE839AC003BAB6F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 639EDF2F1DF1F203006836CD /* DefaultNonRotatingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultNonRotatingViewController.swift; sourceTree = ""; }; 38 | 639EDF311DF1F3A8006836CD /* CustomViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomViewController.swift; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 6367D3B01DE839AC003BAB6F /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 6312D3B61E07E3330061AE14 /* CircleMenu */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 6312D3B71E07E3330061AE14 /* Circle.swift */, 56 | 6312D3B81E07E3330061AE14 /* CircleGestureRecognizer.swift */, 57 | 6312D3B91E07E3330061AE14 /* CircleIconView.swift */, 58 | 6312D3BA1E07E3330061AE14 /* CircleOverlayView.swift */, 59 | 6312D3BB1E07E3330061AE14 /* CircleThumb.swift */, 60 | ); 61 | name = CircleMenu; 62 | path = ../CircleMenu; 63 | sourceTree = ""; 64 | }; 65 | 6367D3AA1DE839AC003BAB6F = { 66 | isa = PBXGroup; 67 | children = ( 68 | 6312D3B61E07E3330061AE14 /* CircleMenu */, 69 | 6367D3B51DE839AC003BAB6F /* CircleMenuDemo */, 70 | 6367D3B41DE839AC003BAB6F /* Products */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | 6367D3B41DE839AC003BAB6F /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 6367D3B31DE839AC003BAB6F /* CircleMenuDemo.app */, 78 | ); 79 | name = Products; 80 | sourceTree = ""; 81 | }; 82 | 6367D3B51DE839AC003BAB6F /* CircleMenuDemo */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 6367D3B61DE839AC003BAB6F /* AppDelegate.swift */, 86 | 639EDF311DF1F3A8006836CD /* CustomViewController.swift */, 87 | 6367D3B81DE839AC003BAB6F /* DefaultRotatingViewController.swift */, 88 | 639EDF2F1DF1F203006836CD /* DefaultNonRotatingViewController.swift */, 89 | 6367D3BA1DE839AC003BAB6F /* Main.storyboard */, 90 | 6367D3BD1DE839AC003BAB6F /* Assets.xcassets */, 91 | 6367D3BF1DE839AC003BAB6F /* LaunchScreen.storyboard */, 92 | 6367D3C21DE839AC003BAB6F /* Info.plist */, 93 | ); 94 | path = CircleMenuDemo; 95 | sourceTree = ""; 96 | }; 97 | /* End PBXGroup section */ 98 | 99 | /* Begin PBXNativeTarget section */ 100 | 6367D3B21DE839AC003BAB6F /* CircleMenuDemo */ = { 101 | isa = PBXNativeTarget; 102 | buildConfigurationList = 6367D3C51DE839AC003BAB6F /* Build configuration list for PBXNativeTarget "CircleMenuDemo" */; 103 | buildPhases = ( 104 | 6367D3AF1DE839AC003BAB6F /* Sources */, 105 | 6367D3B01DE839AC003BAB6F /* Frameworks */, 106 | 6367D3B11DE839AC003BAB6F /* Resources */, 107 | ); 108 | buildRules = ( 109 | ); 110 | dependencies = ( 111 | ); 112 | name = CircleMenuDemo; 113 | productName = CircleMenuDemo; 114 | productReference = 6367D3B31DE839AC003BAB6F /* CircleMenuDemo.app */; 115 | productType = "com.apple.product-type.application"; 116 | }; 117 | /* End PBXNativeTarget section */ 118 | 119 | /* Begin PBXProject section */ 120 | 6367D3AB1DE839AC003BAB6F /* Project object */ = { 121 | isa = PBXProject; 122 | attributes = { 123 | LastSwiftUpdateCheck = 0810; 124 | LastUpgradeCheck = 0810; 125 | ORGANIZATIONNAME = "Kindows Tech Solutions"; 126 | TargetAttributes = { 127 | 6367D3B21DE839AC003BAB6F = { 128 | CreatedOnToolsVersion = 8.1; 129 | DevelopmentTeam = R93M99E72L; 130 | ProvisioningStyle = Automatic; 131 | }; 132 | }; 133 | }; 134 | buildConfigurationList = 6367D3AE1DE839AC003BAB6F /* Build configuration list for PBXProject "CircleMenuDemo" */; 135 | compatibilityVersion = "Xcode 3.2"; 136 | developmentRegion = English; 137 | hasScannedForEncodings = 0; 138 | knownRegions = ( 139 | en, 140 | Base, 141 | ); 142 | mainGroup = 6367D3AA1DE839AC003BAB6F; 143 | productRefGroup = 6367D3B41DE839AC003BAB6F /* Products */; 144 | projectDirPath = ""; 145 | projectRoot = ""; 146 | targets = ( 147 | 6367D3B21DE839AC003BAB6F /* CircleMenuDemo */, 148 | ); 149 | }; 150 | /* End PBXProject section */ 151 | 152 | /* Begin PBXResourcesBuildPhase section */ 153 | 6367D3B11DE839AC003BAB6F /* Resources */ = { 154 | isa = PBXResourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 6367D3C11DE839AC003BAB6F /* LaunchScreen.storyboard in Resources */, 158 | 6367D3BE1DE839AC003BAB6F /* Assets.xcassets in Resources */, 159 | 6367D3BC1DE839AC003BAB6F /* Main.storyboard in Resources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXResourcesBuildPhase section */ 164 | 165 | /* Begin PBXSourcesBuildPhase section */ 166 | 6367D3AF1DE839AC003BAB6F /* Sources */ = { 167 | isa = PBXSourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 6312D3BC1E07E3330061AE14 /* Circle.swift in Sources */, 171 | 6312D3BF1E07E3330061AE14 /* CircleOverlayView.swift in Sources */, 172 | 639EDF321DF1F3A8006836CD /* CustomViewController.swift in Sources */, 173 | 6312D3BE1E07E3330061AE14 /* CircleIconView.swift in Sources */, 174 | 639EDF301DF1F203006836CD /* DefaultNonRotatingViewController.swift in Sources */, 175 | 6312D3BD1E07E3330061AE14 /* CircleGestureRecognizer.swift in Sources */, 176 | 6367D3B91DE839AC003BAB6F /* DefaultRotatingViewController.swift in Sources */, 177 | 6367D3B71DE839AC003BAB6F /* AppDelegate.swift in Sources */, 178 | 6312D3C01E07E3330061AE14 /* CircleThumb.swift in Sources */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | /* End PBXSourcesBuildPhase section */ 183 | 184 | /* Begin PBXVariantGroup section */ 185 | 6367D3BA1DE839AC003BAB6F /* Main.storyboard */ = { 186 | isa = PBXVariantGroup; 187 | children = ( 188 | 6367D3BB1DE839AC003BAB6F /* Base */, 189 | ); 190 | name = Main.storyboard; 191 | sourceTree = ""; 192 | }; 193 | 6367D3BF1DE839AC003BAB6F /* LaunchScreen.storyboard */ = { 194 | isa = PBXVariantGroup; 195 | children = ( 196 | 6367D3C01DE839AC003BAB6F /* Base */, 197 | ); 198 | name = LaunchScreen.storyboard; 199 | sourceTree = ""; 200 | }; 201 | /* End PBXVariantGroup section */ 202 | 203 | /* Begin XCBuildConfiguration section */ 204 | 6367D3C31DE839AC003BAB6F /* Debug */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | CLANG_ANALYZER_NONNULL = YES; 209 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 210 | CLANG_CXX_LIBRARY = "libc++"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_WARN_BOOL_CONVERSION = YES; 214 | CLANG_WARN_CONSTANT_CONVERSION = YES; 215 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 216 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 217 | CLANG_WARN_EMPTY_BODY = YES; 218 | CLANG_WARN_ENUM_CONVERSION = YES; 219 | CLANG_WARN_INFINITE_RECURSION = YES; 220 | CLANG_WARN_INT_CONVERSION = YES; 221 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 222 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 223 | CLANG_WARN_UNREACHABLE_CODE = YES; 224 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 225 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 226 | COPY_PHASE_STRIP = NO; 227 | DEBUG_INFORMATION_FORMAT = dwarf; 228 | ENABLE_STRICT_OBJC_MSGSEND = YES; 229 | ENABLE_TESTABILITY = YES; 230 | GCC_C_LANGUAGE_STANDARD = gnu99; 231 | GCC_DYNAMIC_NO_PIC = NO; 232 | GCC_NO_COMMON_BLOCKS = YES; 233 | GCC_OPTIMIZATION_LEVEL = 0; 234 | GCC_PREPROCESSOR_DEFINITIONS = ( 235 | "DEBUG=1", 236 | "$(inherited)", 237 | ); 238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 240 | GCC_WARN_UNDECLARED_SELECTOR = YES; 241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 242 | GCC_WARN_UNUSED_FUNCTION = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 245 | MTL_ENABLE_DEBUG_INFO = YES; 246 | ONLY_ACTIVE_ARCH = YES; 247 | SDKROOT = iphoneos; 248 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 249 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 250 | }; 251 | name = Debug; 252 | }; 253 | 6367D3C41DE839AC003BAB6F /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_ANALYZER_NONNULL = YES; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INFINITE_RECURSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 271 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 272 | CLANG_WARN_UNREACHABLE_CODE = YES; 273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 275 | COPY_PHASE_STRIP = NO; 276 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 277 | ENABLE_NS_ASSERTIONS = NO; 278 | ENABLE_STRICT_OBJC_MSGSEND = YES; 279 | GCC_C_LANGUAGE_STANDARD = gnu99; 280 | GCC_NO_COMMON_BLOCKS = YES; 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 288 | MTL_ENABLE_DEBUG_INFO = NO; 289 | SDKROOT = iphoneos; 290 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 291 | VALIDATE_PRODUCT = YES; 292 | }; 293 | name = Release; 294 | }; 295 | 6367D3C61DE839AC003BAB6F /* Debug */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 299 | DEVELOPMENT_TEAM = R93M99E72L; 300 | INFOPLIST_FILE = CircleMenuDemo/Info.plist; 301 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 302 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 303 | PRODUCT_BUNDLE_IDENTIFIER = "com.shoaib-ahmed.CircleMenuDemo"; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | SWIFT_VERSION = 3.0; 306 | TARGETED_DEVICE_FAMILY = "1,2"; 307 | }; 308 | name = Debug; 309 | }; 310 | 6367D3C71DE839AC003BAB6F /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 314 | DEVELOPMENT_TEAM = R93M99E72L; 315 | INFOPLIST_FILE = CircleMenuDemo/Info.plist; 316 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 317 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 318 | PRODUCT_BUNDLE_IDENTIFIER = "com.shoaib-ahmed.CircleMenuDemo"; 319 | PRODUCT_NAME = "$(TARGET_NAME)"; 320 | SWIFT_VERSION = 3.0; 321 | TARGETED_DEVICE_FAMILY = "1,2"; 322 | }; 323 | name = Release; 324 | }; 325 | /* End XCBuildConfiguration section */ 326 | 327 | /* Begin XCConfigurationList section */ 328 | 6367D3AE1DE839AC003BAB6F /* Build configuration list for PBXProject "CircleMenuDemo" */ = { 329 | isa = XCConfigurationList; 330 | buildConfigurations = ( 331 | 6367D3C31DE839AC003BAB6F /* Debug */, 332 | 6367D3C41DE839AC003BAB6F /* Release */, 333 | ); 334 | defaultConfigurationIsVisible = 0; 335 | defaultConfigurationName = Release; 336 | }; 337 | 6367D3C51DE839AC003BAB6F /* Build configuration list for PBXNativeTarget "CircleMenuDemo" */ = { 338 | isa = XCConfigurationList; 339 | buildConfigurations = ( 340 | 6367D3C61DE839AC003BAB6F /* Debug */, 341 | 6367D3C71DE839AC003BAB6F /* Release */, 342 | ); 343 | defaultConfigurationIsVisible = 0; 344 | defaultConfigurationName = Release; 345 | }; 346 | /* End XCConfigurationList section */ 347 | }; 348 | rootObject = 6367D3AB1DE839AC003BAB6F /* Project object */; 349 | } 350 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo.xcodeproj/project.xcworkspace/xcuserdata/sufialhussaini.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hu55a1n1/Swift-CircleMenu/b7e166e62f13f4232a8b3ba66b0088131acebcda/CircleMenuDemo/CircleMenuDemo.xcodeproj/project.xcworkspace/xcuserdata/sufialhussaini.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo.xcodeproj/xcuserdata/sufialhussaini.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo.xcodeproj/xcuserdata/sufialhussaini.xcuserdatad/xcschemes/CircleMenuDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo.xcodeproj/xcuserdata/sufialhussaini.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CircleMenuDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 6367D3B21DE839AC003BAB6F 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CircleMenuDemo 4 | // 5 | // Created by Shoaib Ahmed on 11/25/16. 6 | // Copyright © 2016 Shoaib Ahmed / Sufi-Al-Hussaini. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/Assets.xcassets/icon_arrow_up.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_arrow_up.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/Assets.xcassets/icon_arrow_up.imageset/icon_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hu55a1n1/Swift-CircleMenu/b7e166e62f13f4232a8b3ba66b0088131acebcda/CircleMenuDemo/CircleMenuDemo/Assets.xcassets/icon_arrow_up.imageset/icon_arrow_up.png -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/CustomViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomViewController.swift 3 | // CircleMenuDemo 4 | // 5 | // Created by Shoaib Ahmed on 11/25/16. 6 | // Copyright © 2016 Kindows Tech Solutions. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomViewController: UIViewController { 12 | 13 | let numberOfThumbs = 8 14 | var circle: Circle! 15 | 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | prepareView() 20 | prepareCustomCircleMenu() 21 | } 22 | 23 | func prepareView() { 24 | view.backgroundColor = UIColor.lightGray 25 | } 26 | 27 | func prepareCustomCircleMenu() { 28 | circle = Circle(with: CGRect(x: 10, y: 90, width: 300, height: 300), numberOfSegments: numberOfThumbs, ringWidth: 80.0, isRotating: true, iconWidth: 35, iconHeight: 35) 29 | circle?.dataSource = self 30 | circle?.delegate = self 31 | 32 | let overlay = CircleOverlayView(with: circle) 33 | circle?.overlayView?.overlayThumb.arcColor = UIColor.clear 34 | circle?.circleColor = UIColor.clear 35 | 36 | // Customize thumbs 37 | for (_, thumb) in (circle?.thumbs.enumerated())! { 38 | let thumb = thumb as! CircleThumb 39 | 40 | thumb.iconView.highlightedIconColor = UIColor.blue 41 | thumb.iconView.isSelected = false 42 | thumb.iconView.isHidden = false 43 | thumb.separatorStyle = .none 44 | thumb.isGradientFill = false 45 | thumb.arcColor = UIColor.clear 46 | 47 | // Add circular border to icon 48 | thumb.iconView.layer.borderWidth = 1 49 | thumb.iconView.layer.masksToBounds = false 50 | thumb.iconView.layer.borderColor = UIColor.white.cgColor 51 | thumb.iconView.layer.cornerRadius = thumb.iconView.frame.height/2 52 | thumb.iconView.layer.backgroundColor = UIColor.clear.cgColor 53 | thumb.iconView.clipsToBounds = true 54 | } 55 | 56 | // Center circle and overlay 57 | overlay!.center = view.center 58 | circle?.center = view.center 59 | 60 | // Add circle and overlay to view 61 | self.view.addSubview(circle!) 62 | self.view.addSubview(overlay!) 63 | } 64 | 65 | } 66 | 67 | 68 | extension CustomViewController: CircleDelegate, CircleDataSource { 69 | 70 | func circle(_ circle: Circle, didMoveTo segment: Int, thumb: CircleThumb) { 71 | let alert = UIAlertController(title: "Selected", message: "Item with tag: \(segment)", preferredStyle: UIAlertControllerStyle.alert) 72 | alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil)) 73 | self.present(alert, animated: true, completion: nil) 74 | 75 | // thumb.iconView.isSelected = false 76 | // thumb.iconView.isHidden = false 77 | 78 | // Rotate selected icon 79 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 80 | thumb.iconView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI)) 81 | }) 82 | 83 | UIView.animate(withDuration: 0.2, delay: 0.15, options: UIViewAnimationOptions.curveEaseIn, animations: { () -> Void in 84 | thumb.iconView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI * 2)) 85 | }, completion: nil) 86 | } 87 | 88 | func circle(_ circle: Circle, iconForThumbAt row: Int) -> UIImage { 89 | // let thumb = circle.thumbs[row] as! CircleThumb 90 | // thumb.iconView.isSelected = false 91 | // thumb.iconView.isHidden = false 92 | return UIImage(named: "icon_arrow_up")! 93 | } 94 | 95 | } 96 | 97 | 98 | extension UIView { 99 | 100 | var height:CGFloat { 101 | get { 102 | return self.frame.size.height 103 | } 104 | set { 105 | self.frame.size.height = newValue 106 | } 107 | } 108 | 109 | var width:CGFloat { 110 | get { 111 | return self.frame.size.width 112 | } 113 | set { 114 | self.frame.size.width = newValue 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/DefaultNonRotatingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultNonRotatingViewController.swift 3 | // CircleMenuDemo 4 | // 5 | // Created by Shoaib Ahmed on 12/2/16. 6 | // Copyright © 2016 Kindows Tech Solutions. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DefaultNonRotatingViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | prepareView() 16 | prepareNonRotatingCircleMenu() 17 | } 18 | 19 | func prepareView() { 20 | view.backgroundColor = UIColor.lightGray 21 | } 22 | 23 | func prepareNonRotatingCircleMenu() { 24 | // Create circle 25 | let circle = Circle(with: CGRect(x: 10, y: 90, width: 300, height: 300), numberOfSegments: 10, ringWidth: 80.0, isRotating: false) 26 | // Set dataSource and delegate 27 | circle.dataSource = self 28 | circle.delegate = self 29 | 30 | // Position and customize 31 | circle.center = view.center 32 | 33 | // Add to view 34 | self.view.addSubview(circle) 35 | 36 | // NOTE: Do not add overlay for non-rotating circle 37 | } 38 | 39 | } 40 | 41 | 42 | extension DefaultNonRotatingViewController: CircleDelegate, CircleDataSource { 43 | 44 | func circle(_ circle: Circle, didMoveTo segment: Int, thumb: CircleThumb) { 45 | let alert = UIAlertController(title: "Selected", message: "Item with tag: \(segment)", preferredStyle: UIAlertControllerStyle.alert) 46 | alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil)) 47 | self.present(alert, animated: true, completion: nil) 48 | } 49 | 50 | func circle(_ circle: Circle, iconForThumbAt row: Int) -> UIImage { 51 | return UIImage(named: "icon_arrow_up")! 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/DefaultRotatingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultRotatingViewController.swift 3 | // CircleMenuDemo 4 | // 5 | // Created by Shoaib Ahmed on 11/25/16. 6 | // Copyright © 2016 Shoaib Ahmed / Sufi-Al-Hussaini. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DefaultRotatingViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | prepareView() 16 | prepareDefaultCircleMenu() 17 | } 18 | 19 | func prepareView() { 20 | view.backgroundColor = UIColor.lightGray 21 | } 22 | 23 | func prepareDefaultCircleMenu() { 24 | // Create circle 25 | let circle = Circle(with: CGRect(x: 10, y: 90, width: 300, height: 300), numberOfSegments: 10, ringWidth: 80.0) 26 | // Set dataSource and delegate 27 | circle.dataSource = self 28 | circle.delegate = self 29 | 30 | // Position and customize 31 | circle.center = view.center 32 | 33 | // Create overlay with circle 34 | let overlay = CircleOverlayView(with: circle) 35 | 36 | // Add to view 37 | self.view.addSubview(circle) 38 | self.view.addSubview(overlay!) 39 | } 40 | } 41 | 42 | 43 | extension DefaultRotatingViewController: CircleDelegate, CircleDataSource { 44 | 45 | func circle(_ circle: Circle, didMoveTo segment: Int, thumb: CircleThumb) { 46 | let alert = UIAlertController(title: "Selected", message: "Item with tag: \(segment)", preferredStyle: UIAlertControllerStyle.alert) 47 | alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil)) 48 | self.present(alert, animated: true, completion: nil) 49 | } 50 | 51 | func circle(_ circle: Circle, iconForThumbAt row: Int) -> UIImage { 52 | return UIImage(named: "icon_arrow_up")! 53 | } 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /CircleMenuDemo/CircleMenuDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Shoaib Ahmed / Sufi-Al-Hussaini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift-CircleMenu 2 | A rotating circle menu written in Swift 3. 3 | 4 | ## Features 5 | 6 | * Gesture based rotation 7 | * Configurable rotatability 8 | * High customisability 9 | * Simple intuitive API 10 | * Inertia effect 11 | * Ready to use samples 12 | 13 | 14 | ## Screenshots 15 | 16 | Swift-CircleMenu in action in [CETUS](https://itunes.apple.com/us/app/CETUS/id1174919225) iOS App. 17 | 18 | ![simulator screen shot dec 3 2016 9 17 45 pm](https://cloud.githubusercontent.com/assets/7275476/20860974/f140ce60-b99d-11e6-9f68-2178c315df1c.png) 19 | ![simulator screen shot dec 3 2016 9 17 30 pm](https://cloud.githubusercontent.com/assets/7275476/20860970/eb60b91a-b99d-11e6-95eb-1b4fa0b3670b.png) 20 | 21 | 22 | ## Getting Started 23 | 24 | Add this to your `Podfile`: 25 | ``` 26 | pod 'Swift-CircleMenu', :git => 'https://github.com/Sufi-Al-Hussaini/Swift-CircleMenu.git' 27 | ``` 28 | 29 | 30 | ## Usage 31 | 32 | Please look at the demo project provided. 33 | 34 | Basically, you'll need to create a circle and setup its frame & positioning, and add it to your view. 35 | Optionally, you may add an overlay. 36 | 37 | Don't forget to set the `delegate` and `datasource`. 38 | 39 | ```swift 40 | class DefaultRotatingViewController: UIViewController { 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | prepareDefaultCircleMenu() 45 | } 46 | 47 | func prepareDefaultCircleMenu() { 48 | // Create circle 49 | let circle = Circle(with: CGRect(x: 10, y: 90, width: 300, height: 300), numberOfSegments: 10, ringWidth: 80.0) 50 | // Set dataSource and delegate 51 | circle.dataSource = self 52 | circle.delegate = self 53 | 54 | // Position and customize 55 | circle.center = view.center 56 | 57 | // Create overlay with circle 58 | let overlay = CircleOverlayView(with: circle) 59 | 60 | // Add to view 61 | self.view.addSubview(circle) 62 | self.view.addSubview(overlay!) 63 | } 64 | 65 | } 66 | ``` 67 | 68 | Then, you need to conform to the `CircleDelegate` and `CircleDataSource` protocols by implementing the `didMoveTo segment:` and `iconForThumbAt row:` methods. 69 | 70 | ```swift 71 | 72 | extension DefaultRotatingViewController: CircleDelegate, CircleDataSource { 73 | 74 | func circle(_ circle: Circle, didMoveTo segment: Int, thumb: CircleThumb) { 75 | let alert = UIAlertController(title: "Selected", message: "Item with tag: \(segment)", preferredStyle: UIAlertControllerStyle.alert) 76 | alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil)) 77 | self.present(alert, animated: true, completion: nil) 78 | } 79 | 80 | func circle(_ circle: Circle, iconForThumbAt row: Int) -> UIImage { 81 | return UIImage(named: "icon_arrow_up")! 82 | } 83 | 84 | } 85 | 86 | ``` 87 | The above code will give you the default minimal circle menu shown below. 88 | 89 | ![simulator screen shot dec 3 2016 9 12 50 pm](https://cloud.githubusercontent.com/assets/7275476/20860954/9e4d5926-b99d-11e6-84c7-3dfc46ab07ea.png) 90 | 91 | You can disable rotation using `Circle`'s optional default constructor parameter `isRotating` like so: 92 | ```swift 93 | let circle = Circle(with: CGRect(x: 10, y: 90, width: 300, height: 300), numberOfSegments: 10, ringWidth: 80.0, isRotating: false) 94 | ``` 95 | 96 | More examples to be added soon. :) 97 | 98 | 99 | ## License 100 | 101 | Swift-CircleMenu is licensed under the MIT license. 102 | 103 | 104 | ## Why Swift-CircleMenu? 105 | 106 | For an app I was developing recently, I wanted something like [Android-CircleMenu](https://github.com/szugyi/Android-CircleMenu), i.e. a rotatable circle menu. 107 | I came across a number of circle menus for iOS on github, but only one supported rotation with inertia effect - [CDPieMenu](https://github.com/wokalski/CDPieMenu). 108 | The problem with CDPieMenu though, is that it is written in Obj-C and isn't being maintained currently. 109 | So, I decided to rewrite CDPieMenu in swift and include in it all features I required in my app, and make it available publicly. 110 | 111 | 112 | ## Credits 113 | 114 | Swift-CircleMenu is (more than) heavily inspired by [CDPieMenu](https://github.com/wokalski/CDPieMenu) - an Obj-C library written by [Wojtek Czekalski](https://github.com/wokalski). 115 | In its current form, this project is essentially a rewrite of CDPieMenu in Swift, with multiple bug-fixes and added features & examples. 116 | Special thanks to Wojtek Czekalski for his awesome CDPieMenu library! 117 | --------------------------------------------------------------------------------