Upvote:3
Aprove answer
class ViewController: UIViewController {
private var progressView: GradientProgressView?
override func viewDidLoad() {
super.viewDidLoad()
let progressView = GradientProgressView(frame: .init(x: 30.0, y: 30.0, width: 280.0, height: 350.0))
progressView.backgroundColor = UIColor.lightGray // Just to debug
progressView.transform = CGAffineTransform(rotationAngle: -.pi*0.5)
progressView.points = {
let count = 200
let minimumRadius: CGFloat = 0.9
let maximumRadius: CGFloat = 1.1
return (0...count).map { index in
let progress: CGFloat = CGFloat(index) / CGFloat(count)
let angle = CGFloat.pi * 2.0 * progress
let radius = CGFloat.random(in: minimumRadius...maximumRadius)
return .init(x: cos(angle)*radius, y: sin(angle)*radius)
}
}()
view.addSubview(progressView)
self.progressView = progressView
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(animateProgress)))
}
@objc private func animateProgress() {
let duration: TimeInterval = 1.0
let startDate = Date()
Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { [weak self] timer in
guard let self = self else {
timer.invalidate()
return
}
let progress = Date().timeIntervalSince(startDate)/duration
if progress >= 1.0 {
timer.invalidate()
}
self.progressView?.progress = max(0.0, min(CGFloat(progress), 1.0))
}
}
}
private extension ViewController {
class GradientProgressView: UIView {
var points: [CGPoint]? { didSet { setNeedsDisplay() } }
var progress: CGFloat = 0.7 { didSet { setNeedsDisplay() } }
override func draw(_ rect: CGRect) {
super.draw(rect)
let actualProgress = max(0.0, min(progress, 1.0))
guard actualProgress > 0.0 else { return } // Nothing to draw
let willClipAsProgress = actualProgress < 1.0
guard let context = UIGraphicsGetCurrentContext() else { return }
let lineWidth: CGFloat = 5.0
guard let points = points else { return }
guard let path = generatePath(withPoints: points, inFrame: bounds.insetBy(dx: lineWidth, dy: lineWidth)) else { return }
if willClipAsProgress {
context.saveGState()
createProgressClippingPath().addClip()
}
drawGradient(path: path, context: context)
drawLine(path: path, lineWidth: lineWidth, context: context)
if willClipAsProgress {
context.restoreGState()
}
}
private func createProgressClippingPath() -> UIBezierPath {
let endAngle = CGFloat.pi*2.0*progress
let maxRadius: CGFloat = max(bounds.width, bounds.height) // we simply need one that is large enough.
let path = UIBezierPath()
let center: CGPoint = .init(x: bounds.midX, y: bounds.midY)
path.move(to: center)
path.addArc(withCenter: center, radius: maxRadius, startAngle: 0.0, endAngle: endAngle, clockwise: true)
return path
}
private func drawGradient(path: UIBezierPath, context: CGContext) {
context.saveGState()
path.addClip() // This will be discarded once restoreGState() is called
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [UIColor.blue, UIColor.green].map { $0.cgColor } as CFArray, locations: [0.0, 1.0])!
context.drawRadialGradient(gradient, startCenter: CGPoint(x: bounds.midX, y: bounds.midY), startRadius: 0.0, endCenter: CGPoint(x: bounds.midX, y: bounds.midY), endRadius: min(bounds.width, bounds.height), options: [])
context.restoreGState()
}
private func drawLine(path: UIBezierPath, lineWidth: CGFloat, context: CGContext) {
UIColor.black.setStroke()
path.lineWidth = lineWidth
path.stroke()
}
private func generatePath(withPoints points: [CGPoint], inFrame frame: CGRect) -> UIBezierPath? {
guard points.count > 2 else { return nil } // At least 3 points
let pointsInPolarCoordinates: [(angle: CGFloat, radius: CGFloat)] = points.map { point in
let radius = (point.x*point.x + point.y*point.y).squareRoot()
let angle = atan2(point.y, point.x)
return (angle, radius)
}
let maximumPointRadius: CGFloat = pointsInPolarCoordinates.max(by: { $1.radius > $0.radius })!.radius
guard maximumPointRadius > 0.0 else { return nil } // Not all points may be centered
let maximumFrameRadius = min(frame.width, frame.height)*0.5
let radiusScale = maximumFrameRadius/maximumPointRadius
let normalizedPoints: [CGPoint] = pointsInPolarCoordinates.map { polarPoint in
.init(x: frame.midX + cos(polarPoint.angle)*polarPoint.radius*radiusScale,
y: frame.midY + sin(polarPoint.angle)*polarPoint.radius*radiusScale)
}
let path = UIBezierPath()
path.move(to: normalizedPoints[0])
normalizedPoints[1...].forEach { path.addLine(to: $0) }
path.close()
return path
}
}
}
Credit Goes to: stackoverflow.com
Related question with same questions but different answers
- IOS - DRAW A VIEW WITH GRADIENT BACKGROUND IN IOS
- APPLYING GRADIENT COLOUR TO STORYBOARD VIEW WITH AUTO LAYOUT IN IOS
- FSCALENDAR SWIFT ISSUE WITH ONE WEEK VIEW IN IOS
- OVERRIDE MODAL VIEW CONTROLLER ELEVATED BACKGROUND COLOR? IN IOS
- HOW TO PRESENT VIEW CONTROLLER WITH TAB BAR IN IOS
- SWIFTUI - VIEW WITH CONDITION IN IOS
- IOS AUTO LAYOUT - ISSUE WITH CONSTRAINS WITHIN STACK VIEW IN IOS
- HOW TO HANDLE OPEN ALL/CLOSE ALL IN TABLEVIEW CELL WITH EACH CELL HAS VIEW MORE/VIEW LESS OPTIONS IN IOS SWIFT IN IOS
- HOW TO LOAD DATA FROM MORE THEN ONE LOCAL JSON FILE TO THE LABELS IN TABLE VIEW USING SWIFT IN IOS
- ADDING GRADIENT TO UILABEL THROUGH EXTENSION IN IOS
- IS THERE ANY WAY TO BUILD IOS 15 APPS WITH XCODE 12.X? IN IOS
- REACT NATIVE 0.64.2/XCODE 12.5/13 BETA: MULTIPLE COMMANDS PRODUCE WITH NEW BUILD SYSTEM (DEFAULT) IN IOS
- EXPANDING UITEXTVIEW WITH UIBUTTON INSTEAD OF AUTOMATICALLY BASED ON CONTENT IN IOS
- I WANT TO PASS AN INSTANCE OF TYPE USER OBJECT BETWEEN VIEW CONTROLLERS IN SWIFT IN IOS
- TROUBLE UNDERSTANDING XCODE'S VARIABLES VIEW IN IOS
- FRAMEWORK IS RED IN XCODE BUT NO ERROR WITH BUILDING IN IOS
- IS IT SENSIBLE TO MARK FIRESTORE THROWING FUNCTIONS WITH TRY! IN SWIFT? IN IOS
- HOW TO HAVE TWO UILABEL WITH 0 NUMBER OF LINES INSIDE A SCROLLVIEW IN IOS
- HOW DO I CALL A FUNCTION ON A VIEW WHEN THE @OBSERVEDOBJECT IS UPDATED IN IOS
- WHAT'S THE BEST WAY FOR APPLE WATCH TO COMMUNICATE WITH IPHONE? IN IOS
- LOAD 3D USDZ MODEL FROM NETWORK IN SCENEKIT VIEW DOES NOT WORK? IN IOS
- SWIFTUI CHANGE OBSERVED OBJECT FROM ANOTHER VIEW IN IOS
- HOW DO I PASS DATA FROM ONE SWIFTUI VIEW FILE TO ANOTHER? IN IOS
- HOW TO GET RESULT FROM .M FUNCTION TO .MM CLASS OBJECTIVE-C MIXED WITH QT IN IOS
- HOW TO ADD VIEW WHICH WILL BE TOP OF ALL VIEW IN SWIFTUI IN IOS
- HOW DO I TEST THE APP WITH APPLE DEVELOPER PROGRAM? IN IOS
- WHEN DEALING WITH LOCALIZABLE.STRINGSDICT, WHY STRING.LOCALIZEDSTRINGWITHFORMAT IS ABLE TO PRODUCE DIFFERENT OUTPUT WITH SAME INPUT? IN IOS
- @VIEWBUILDER THROWS ERROR WHEN INIT WITH OTHER PROPERTIES IN IOS
- HOW TO MONITOR BATTERY LEVEL WHILE APP IS IN BACKGROUND THEN SEND PUSH NOTIFICATION? IOS 14 IN IOS
- APP DEPENDEING ON COCOAPOD DISTRIBUTING AS XCFRAMEWORK WITH ITS OWN DEPENDENCIES CRASHES ON STARTUP WITH DYLD ERROR EVEN IF I USE POST INSTALL SCRIPT IN IOS
- TRIPLE QUESTION MARK ("???") OPERATOR IN SWIFT? IN IOS
- HOW DO I USE FASTLANE TO COMPILE MY IOS APP INTO AN .XCARCHIVE, THEN SEPARATELY SIGN AND MAKE AN IPA? IN IOS
- STRUGGLING TO TURN A DOUBLE INTO A STRING TO PUT INSIDE A TEXT FROM AN OBJECT ARRAY SWIFTUI (SWIFT) IN IOS
- FLUTTER, IOS, APP WITH NAME __FIRAPP_DEFAULT DOES NOT EXIST IN IOS
- HOW DO I REMOVE MACOS APP PAGE VERSION FROM APPSTORECONNECT FOR IOS APP IN IOS
- SWIFT INVALID REDECLARATION CLASS DEFAULT INIT IN IOS
- EXECVE ASSEMBLY SHELLCODE FOR AARCH64 IOS DARWIN IN IOS
- HOW TO SET TEXTFIELD IN INPUTACCESSORYVIEW AS FIRST RESPONDER [SWIFT] IN IOS
- IOS OPENING A STAND ALONE NAVIGATION CONTROLLER AS MODAL IN IOS
- HOW TO COMBINE SWIFT CODE(CLASS) WITH OBJECTIVE-C? IN IOS
- HOW TO CHECK GIVEN STRING IS IN HTML FORMAT OR NOT IN SWIFT IN IOS
- COCOAPODS COULD NOT FIND COMPATIBLE VERSIONS FOR POD IN IOS
- EXTENDING A TYPE TO HAVE MULTIPLE VALUES FOR THE SAME ASSOCIATEDTYPE IN IOS
- HOW TO FIX DOCUMENTATION WARNINGS XCODE IN IOS
- "IPHONE CREATED A STRONG PASSWORD FOR THIS WEBSITE" POPUP APPEARING WHEN TAPPING ON A SECURE TEXT FIELD IN IOS
- SWIFTUI: DISABLE/RE-ENABLE BUTTON BASED ON WEBVIEW STATE IN IOS
- OPTIONAL OBJECT TO NON-OPTIONAL ARRAY IN FUNCTIONAL PROGRAMMING IN IOS
- UPDATING PROGRAMMATICALLY CREATED LABEL NAVIGATION BAR ITEM IN IOS
- "'_' CAN ONLY APPEAR IN A PATTERN OR ON THE LEFT SIDE OF AN ASSIGNMENT"-ERROR WHEN CREATING CLOSURE IN IOS
- HOW TO CALL AN API IN APPLICATIONWILLTERMINATE( ) AND GET SOME TIME FOR RESPONSE FROM THE SERVER IN IOS
- UISTACKVIEW WITH UISEGMENTEDCONTROL IN IOS
- WHAT WILL BE THE ISSUE WHEN CRASH LOG CONTAINS _DISPATCH_CALL_BLOCK_AND_RELEASE + 24 IN IOS
- HOW TO DISPLAY REAL CLOCK TIME AFTER FETCHING TIME FROM API IOS IN IOS
- HOW TO CREATE A SINGLETON THAT I CAN UPDATE IN SWIFTUI IN IOS
- CHANGE TRANSITION TO ONE VIEWCONTROLLER IN A UINAVIGATIONCONTROLLER WHILE KEEPING BACK-SWIPE IN IOS
- HORIZONTAL UISTACKVIEW STRETCHING SUBVIEW IN IOS
- RETRIEVING DATA USING URLSESSION AND VIEWMODEL IN IOS