How to Draw a Circle in Xcode
Update annotation: Lorenzo Boaro updated this tutorial for iOS 12, Xcode 10 and Swift 4.ii. Ray Wenderlich wrote the original.
Welcome dorsum to another tutorial in our Core Graphics tutorial series! This series covers how to become started with Cadre Graphics. Core Graphics is a two-dimensional drawing engine with path-based drawing that helps you to create rich UIs.
In this tutorial, yous'll learn how to draw arcs and paths. In detail, yous'll enhance each footer of a grouped table view by calculation a cracking arc on the lesser, a linear gradient and a shadow that fits the bend of the arc. All of that by using the power of Core Graphics!
Getting Started
For this tutorial, you'll utilise LearningAgenda, an iOS app that lists the tutorials you lot want to learn and the ones you've already learned.
Offset by downloading the starter projection using the Download Materials push button at the top or bottom of this tutorial. Once downloaded, open LearningAgenda.xcodeproj in Xcode.
To keep you focused, the starter project has everything unrelated to arcs and paths already ready for you.
Build and run the app, and you'll encounter the post-obit initial screen:
Every bit you can see, there is a grouped table consisting of ii sections, each with a title and three rows. All the work you're going to do here volition create arced footers beneath each section.
Enhancing the Footer
Before taking on your challenge, yous need to create and fix a custom footer that will behave as the placeholder for your future work.
To create the class for your shiny new footer, right-click the LearningAgenda binder and select New File. Next, cull Swift File and proper name the file CustomFooter.swift.
Switch over to CustomFooter.swift file and replace its content with the following code:
import UIKit class CustomFooter: UIView { override init(frame: CGRect) { super.init(frame: frame) isOpaque = true backgroundColor = .articulate } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func draw(_ rect: CGRect) { let context = UIGraphicsGetCurrentContext()! UIColor.cerise.setFill() context.fill up(bounds) } }
Here, you override init(frame:)
to set up isOpaque
truthful. Yous as well prepare the groundwork color to articulate.
Note: The isOpaque
belongings should not be used when the view is fully or partially transparent. Otherwise, the results could be unpredictable.
You as well override init?(coder:)
since information technology'due south mandatory, simply you lot don't provide any implementation since you will not create your custom footer in Interface Builder.
depict(_:)
provides a custom rect content using Core Graphics. You lot set red every bit the fill colour to comprehend the entire bounds of the footer itself.
At present, open TutorialsViewController.swift and add the following ii methods to the UITableViewDelegate
extension at the bottom of the file:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 30 } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return CustomFooter() }
The above methods combine to form a custom footer of 30 points in height.
Build and run the project and, if all works well, y'all should see the following:
Back to Business
OK, now that you have a placeholder view in place, information technology'due south fourth dimension to pretty it up. Only first, here's an thought of what you're going for.
Note the post-obit about the image above:
- The footer has a swell arc on the lesser.
- A gradient, from lite gray to darker greyness, is applied to the footer.
- A shadow fits the curve of the arc.
The Math Behind Arcs
An arc is a curved line that represents a role of a circumvolve. In your case, the arc you want for the bottom of the footer is the top chip of a very large circle, with a very large radius, from a certain get-go angle to a certain end angle.
And then how do you describe this arc to Core Graphics? Well, the API that you're going to use is called addArc(centre:radius:startAngle:endAngle:clockwise:)
, an instance method of CGContext
. The method expects the following five inputs:
- The center bespeak of the circumvolve.
- The radius of the circle.
- The starting signal of the line to draw, also know equally the showtime angle.
- The catastrophe point of the line to draw, as well known as the cease angle.
- The direction in which the arc is created.
But darn it, you don't know any of that, so what in the world are you supposed to do?!
That is where some simple math comes to the rescue. You lot tin can actually calculate all of that from what y'all do know!
The first thing you lot know is the size of the bounding box for where you lot want to describe the arc:
The second thing y'all know is an interesting math theorem called the Intersecting Chord Theorem. Basically, this theorem states that, if you describe ii intersecting chords in a circle, the product of the line segments of the first chord will exist equal to the product of the segments of the 2nd chord. Recall, a chord is a line that connects 2 points in a circle.
Notation: If you want to sympathize why that is, visit the link to a higher place — it has a cool little JavaScript demo you tin play with.
Armed with these two $.25 of knowledge, look what happens if you draw two chords similar the following:
And so, depict one line connecting the bottom points of your arc rect and another line from the top of the arc down to the bottom of the circumvolve.
If you lot do that, y'all know a, b, and c, which lets you figure out d.
Then d would be: (a * b) / c. Substituting that out, it's:
// Just substituting... allow d = ((arcRectWidth / ii) * (arcRectWidth / 2)) / (arcRectHeight); // Or more merely... let d = pw(arcRectWidth, ii) / (four * arcRectHeight);
And now that you lot know c and d, you can summate the radius with the following formula: (c + d) / 2:
// But substituting... let radius = (arcRectHeight + (prisoner of war(arcRectWidth, 2) / (4 * arcRectHeight))) / 2; // Or more only... allow radius = (arcRectHeight / 2) + (pow(arcRectWidth, ii) / (8 * arcRectHeight));
Squeamish! Now that you know the radius, you can get the center by only subtracting the radius from the center indicate of your shadow rect:
let arcCenter = CGPoint(arcRectTopMiddleX, arcRectTopMiddleY - radius)
Once you know the center point, radius and arc rect, you can compute the offset and end angles with a bit of trigonometry:
You'll start by figuring out the angle shown in the diagram, here. If you retrieve SOHCAHTOA, yous might recall the cosine of an angle equals the length of the adjacent edge of the triangle divided past the length of the hypotenuse.
In other words, cosine(angle) = (arcRectWidth / 2) / radius
. So, to become the angle, you only take the arc-cosine, which is the changed of the cosine:
let bending = acos((arcRectWidth / 2) / radius)
And now that yous know that angle, getting the beginning and end angles should exist rather simple:
Nice! Now that you lot understand how, y'all can put it all together equally a function.
Note: By the style, there's actually an even easier way to depict an arc similar this using the addArc(tangent1End:tangent2End:radius:)
method bachelor in CGContext
type.
Cartoon Arcs and Creating Paths
The first matter yous add is a mode to convert degrees to radians. To exercise it, you'll use the Foundation Units and Measurements APIs introduced past Apple in iOS 10 and macOS 10.12.
Note: The Foundation framework provides a robust fashion to piece of work with and represent physical quantities. Other than angles, it provides several built-in unit of measurement types like speed, duration, etc.
Open Extensions.swift and paste the post-obit code at the finish of the file:
typealias Angle = Measurement<UnitAngle> extension Measurement where UnitType == UnitAngle { init(degrees: Double) { cocky.init(value: degrees, unit: .degrees) } func toRadians() -> Double { return converted(to: .radians).value } }
In the code above, you ascertain an extension on the Measurement
blazon restricting its usage to bending units. init(degrees:)
only works with angles in terms of degrees. toRadians()
allows you to convert degrees to radiants.
Note: The conversion from degrees to radians, and vice versa, can also be performed using the formula radians = degrees * π / 180
.
Remaining in Extensions.swift file, find the extension block for CGContext
. Before its last curly caryatid, paste the post-obit code:
static func createArcPathFromBottom( of rect: CGRect, arcHeight: CGFloat, startAngle: Angle, endAngle: Angle ) -> CGPath { // 1 let arcRect = CGRect( x: rect.origin.x, y: rect.origin.y + rect.height, width: rect.width, height: arcHeight) // 2 let arcRadius = (arcRect.summit / two) + pow(arcRect.width, ii) / (8 * arcRect.height) allow arcCenter = CGPoint( x: arcRect.origin.x + arcRect.width / 2, y: arcRect.origin.y + arcRadius) allow angle = acos(arcRect.width / (2 * arcRadius)) let startAngle = CGFloat(startAngle.toRadians()) + bending permit endAngle = CGFloat(endAngle.toRadians()) - angle let path = CGMutablePath() // three path.addArc( middle: arcCenter, radius: arcRadius, startAngle: startAngle, endAngle: endAngle, clockwise: imitation) path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY)) path.addLine(to: CGPoint(x: rect.minX, y: rect.minY)) path.addLine(to: CGPoint(x: rect.minY, y: rect.maxY)) // four render path.copy()! }
In that location'southward quite a bit going on here, then this is how it breaks down:
- This role takes a rectangle of the unabridged area and a float of how big the arc should exist. Remember, the arc should be at the bottom of the rectangle. You lot calculate
arcRect
given those two values. - And then, you effigy out the radius, eye, first and end angles with the math discussed in a higher place.
- Next, yous create the path. The path will consist of the arc and the lines around the edges of the rectangle in a higher place the arc.
- Finally, you render immutable copy of the path. You lot don't want the path to exist modified from outside the function.
Annotation: Unlike the other functions available in CGContext
extension, createArcPathFromBottom(of:arcHeight:startAngle:endAngle:)
returns a CGPath
. This is because the path will be reused many times. More on that later.
Now that you have a helper method to draw arcs in identify, it'due south time to supervene upon your rectangular footer with your new curvy, arrondi one.
Open CustomFooter.swift and replace draw(_:)
with the post-obit lawmaking:
override func draw(_ rect: CGRect) { let context = UIGraphicsGetCurrentContext()! allow footerRect = CGRect( x: bounds.origin.x, y: premises.origin.y, width: bounds.width, height: bounds.acme) var arcRect = footerRect arcRect.size.elevation = 8 context.saveGState() let arcPath = CGContext.createArcPathFromBottom( of: arcRect, arcHeight: iv, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 360)) context.addPath(arcPath) context.clip() context.drawLinearGradient( rect: footerRect, startColor: .rwLightGray, endColor: .rwDarkGray) context.restoreGState() }
Subsequently the customary Cadre Graphics setup, y'all create a bounding box for the entire footer surface area and the area where yous want the arc to be.
And so, you get the arc path by calling createArcPathFromBottom(of:arcHeight:startAngle:endAngle:)
, the static method you merely wrote. You lot tin then add the path to your context and prune to that path.
All further drawing volition exist restricted to that path. Then, y'all tin utilize drawLinearGradient(rect:startColor:endColor:)
found in Extensions.swift to draw a gradient from light gray to darker gray.
Over again, build and run the app. If all works correctly, you should see the following screen:
Looks decent, but you need to shine it up a bit more.
Clipping, Paths and the Even-Odd Rule
In CustomFooter.swift add the post-obit to the bottom of draw(_:)
:
context.addRect(footerRect) context.addPath(arcPath) context.clip(using: .evenOdd) context.addPath(arcPath) context.setShadow( get-go: CGSize(width: 0, height: ii), mistiness: iii, colour: UIColor.rwShadow.cgColor) context.fillPath()
OK, there's a new, and very important, concept going on here.
To draw a shadow, you enable shadow cartoon, then make full a path. Core Graphics will so fill the path and also draw the appropriate shadow underneath.
But you've already filled the path with a gradient, and so you don't desire to overwrite that area with a colour.
Well, that sounds like a job for clipping! Y'all can set clipping then that Core Graphics will only depict in the portion outside the footer area. Then, you tin can tell it to fill the footer expanse and draw the shadow. Since its clipped, the footer expanse fill up will exist ignored, but the shadow will show through.
But you lot don't have a path for this — the only path you have is for the footer surface area, non the outside.
You can easily get a path for the outside based on the inside through a bang-up ability of Core Graphics. You but add together more one path to the context and then add clipping using a specific rule provided past Core Graphics.
When you add more than one path to a context, Core Graphics needs some way to decide which points should and shouldn't be filled. For example, you could have a donut shape where the outside is filled but the inside is empty, or a donut-hole shape where the inside is filled but the outside is empty.
You can specify different algorithms to let Core Graphics know how to handle this. The algorithm you'll use in this tutorial is EO, or even-odd.
In EO, for any given bespeak, Core Graphics volition depict a line from that point to the outside of the drawing expanse. If that line crosses an odd number of points, it will exist filled. If it crosses an even number of points, it will not be filled.
Here'due south a diagram showing this from the Quartz2D Programming Guide:
So, by calling the EO variant, you lot're telling Core Graphics that, even though y'all've added ii paths to the context, it should care for it equally one path following the EO rule. And then, the outside part, which is the entire footer rect, should be filled, but the inner part, which is the arc path, should not. You tell Core Graphics to clip to that path and only depict in the outside area.
In one case you accept the clipping area set up, yous add the path for the arc, set up upwards the shadow and make full the arc. Of course, since information technology's clipped, cypher will actually be filled, but the shadow will even so be drawn in the exterior area!
Build and run the project and, if all goes well, yous should now see a shadow underneath the footer:
Congratulations! You've created custom table view footers using Core Graphics!
Where to Go From Here?
Y'all can download the completed version of the project using the Download Materials button at the elevation or the bottom of this tutorial.
Past following this tutorial, you lot've learned how to create arcs and paths. Now you'll exist able to utilize these concepts directly to your apps!
If you desire to learn more than near Cadre Graphics have a look at Quartz 2D Programming Guide.
In the meantime, if you have any questions or comments, please join the forum give-and-take below!
Source: https://www.raywenderlich.com/349664-core-graphics-tutorial-arcs-and-paths
0 Response to "How to Draw a Circle in Xcode"
Post a Comment